X86 Memory Access Segmentation Fault

x86 memory access segmentation fault

On Linux(x86) - although you have a virtual address range of 4gb in your process, not all of it is accessible. The upper 1gb is where the kernel resides, and there are areas of low memory that can't be used. Virtual memory address 0xfff can't be written to or read from (by default) so your program crashes with a segfault.

In a followup comment you suggested you were intending to create a heap in assembler. That can be done, and one method is to use the sys_brk system call. It is accessed via int 0x80 and EAX=45 . It takes a pointer in EBX representing the new top of the heap. Generally the bottom of the heap area is initialized to the area just beyond your programs data segment(above your program in memory). To get the address of the initial heap location you can call sys_break with EBX set to 0. After the system call EAX will be the current base pointer of the heap. You can save that away when you need to access your heap memory or allocate more heap space.

This code provides an example for purposes of clarity (not performance), but might be a starting point to understanding how you can manipulate the heap area:

SECTION .data
heap_base: dd 0 ; Memory address for base of our heap

SECTION .text
global _start
_start:
; Use `brk` syscall to get current memory address
; For the bottom of our heap This can be achieved
; by calling brk with an address (EBX) of 0
mov eax, 45 ; brk system call
xor ebx, ebx ; don't request additional space, we just want to
; get the memory address for the base of our processes heap area.
int 0x80
mov [heap_base], eax ; Save the heap base

;Now allocate some space (8192 bytes)
mov eax, 45 ; brk system call
mov ebx, [heap_base] ; ebx = address for base of heap
add ebx, 0x2000 ; increase heap by 8192 bytes
int 0x80

; Example usage
mov eax, [heap_base] ; Get pointer to the heap's base
mov dword [eax+0xFFF], 25 ; mov value 25 to DWORD at heapbase+0xFFF

;Exit the program
mov eax, 1
xor ebx, ebx
int 0x80

Segmentation fault when popping x86 stack to access function arg

This is a possible assembly code of int x = plus_10(40);

        push    40                      ; push argument
call plus_10 ; call function
retadd: add esp, 4 ; clean up stack (dummy pop)
; result of the function call is in EAX, per the calling convention

; if compiled without optimization, the caller might just store it:
mov DWORD PTR [ebp-x], eax ; store return value
; (in eax) in x

Now when you call plus_10, the address retadd is pushed on the stack by the call instruction. It's effectively a push+jmp, and ret is effectively pop eip.

So your stack looks like this in the plus_10 function:

|  ...   |
+--------+
| 40 | <- ESP+4 points here (the function argument)
+--------+
| retadd | <- ESP points here
+--------+

ESP points to a memory location that contains the return address.

Now if you use pop edx the return address goes into edx and the stack looks like this:

|  ...   |
+--------+
| 40 | <- ESP points here
+--------+

Now if you execute ret at this point, the program will actually jump to address 40 and most likely segfault or behave in some other unpredictable way.

The actual assembly code generated by the compiler may be different, but this illustrates the problem.


BTW, a more efficient way to write your function is this: it's what most compilers would do with optimization enabled, for a non-inline version of this tiny function.

global plus_10
plus_10:
mov eax, [esp+4] ; retval = first arg
add eax, 10 ; retval += 10
ret

This is smaller and slightly more efficient than

    mov   eax,  10
add eax, [esp+4] ; decode to a load + add.
ret

Segmentation fault when calling x86 Assembly function from C program

  1. Your function has no epilogue. You need to restore %ebp and pop the stack back to where it was, and then ret. If that's really missing from your code, then that explains your segfault: the CPU will go on executing whatever garbage happens to be after the end of your code in memory.

  2. You clobber (i.e. overwrite) the %ebx register which is supposed to be callee-saved. (You mention following the x86 calling conventions, but you seem to have missed that detail.) That would be the cause of your next segfault, after you fixed the first one. If you use %ebx, you need to save and restore it, e.g. with push %ebx after your prologue and pop %ebx before your epilogue. But in this case it is better to rewrite your code so as not to use it at all; see below.

  3. movzbl loads an 8-bit value from memory and zero-extends it into a 32-bit register. Here the parameters are int so they are already 32 bits, so plain movl is correct. As it stands your function would give incorrect results for any arguments which are negative or larger than 255.

  4. You're using an unnecessary number of registers. You could move the first operand for the addition directly into %eax rather than putting it into %ebx and adding it to zero. And on x86 it is not necessary to get both operands into registers before adding; arithmetic instructions have a mem, reg form where one operand can be loaded directly from memory. With this approach we don't need any registers other than %eax itself, and in particular we don't have to worry about %ebx anymore.

I would write:

.text

# Here, we define a function addition
.global addition
addition:
# Prologue:
push %ebp
movl %esp, %ebp

# load first argument
movl 8(%ebp), %eax
# add second argument
addl 12(%ebp), %eax

# epilogue
movl %ebp, %esp # redundant since we haven't touched esp, but will be needed in more complex functions
pop %ebp
ret

In fact, you don't need a stack frame for this function at all, though I understand if you want to include it for educational value. But if you omit it, the function can be reduced to

.text
.global addition
addition:
movl 4(%esp), %eax
addl 8(%esp), %eax
ret

Why exactly is Segmentation Fault still a thing in C

It's not a thing in C specifically, it's a thing in a Unix-like OS. Any language that's not memory-safe (makes it possible to try to access an unmapped page) can compile to an executable that segfaults. Including hand-written assembly or Fortran. But yes, C and C++ are two of the most widely used languages that aren't memory-safe.

And yes, the name is archaic; Unix is old and there was no need to rename SIGSEGV as the signal the kernel delivers when user-space makes the CPU fault by accessing memory it didn't map. And doing so would have broken lots of code that used that constant, and just changing the English text string for strerror / perror for it to "invalid page fault" also wouldn't have been particularly helpful, although possible since those messages are I think baked into libc. But then different libc versions would have different messages for years around the changeover, not worth the trouble.

In a system using paging, any access to a page that's "not present" in the hardware page tables, or only readable when you're trying to write or whatever, causes the CPU to take an exception. The kernel's page-fault exception handler checks if the page should be accessible, and if so pages it in from disk, does copy-on-write, or whatever. (major or minor page fault respectively). If not, the page fault is "invalid", and the kernel delivers a SIGSEGV signal to the process.

Similarly archaic is SIGFPE (Floating Point Exception) for arithmetic exceptions in general, the only one of which that can actually fault by default on most machines is integer division. (The default FP environment has all FP exceptions masked, so they just set sticky flags instead raising an exception in machine code.) The POSIX standard requires that if a signal is going to be delivered because of an arithmetic exception, it must be SIGFPE.

Similarly, by now SIGSEGV has been standardized by POSIX and other Unix standards, so the time in very early Unix days when anyone could have plausibly changed it has long passed.

(Some systems can also deliver SIGBUS for other kinds of bad-address errors, e.g. Solaris on SPARC delivers SIGBUS for unaligned access.)

Also note that some other kinds of permission errors are overloaded onto SIGSEGV. For example, trying to execute a privileged instruction like x86 lgdt under a Linux results in a SIGSEGV. (In that case, user-space would literally be trying to take over the segmentation mechanism that is still used to define what mode the CPU operates in, e.g. 16 vs. 32 vs 64-bit code segment in long mode.) Same for misaligned SSE SIMD instructions. So it's not strictly for invalid page faults.



Naming history?

Executables also have "segments" like text and data, where the .text and .data sections are linked respectively. "The heap" used to be mostly contiguous, growing after .data / .bss (via brk system calls, before mmap(MAP_ANONYMOUS) or mapping pages from /dev/zero was a thing), so it's possible the "segmentation fault" term didn't seem like such nonsense to the designers even after OSes started using paging instead of CPU segmentation for memory protection, because the "segments" of an executable still got mapped to contiguous ranges of pages in the memory image of a process.

I don't know the history details around naming of Unix signals vs. its development on PDP-8 and PDP-11 hardware with or without memory-protection features, although apparently some models of PDP-11 had some form of memory protection, and even virtual memory



TL:DR

There are two hard problems in computer science: Cache invalidation, and naming things, and off-by-one errors.

asm X86 - segmentation fault?

Thanks to @Michael Petch for spotting the syntax error. In lines such as "addl 8,%esp" i did not put the dollar sign, which signifies a value and not a memory address as the instruction is immediate addressing. However i miseed the dollar sign which makes it into a memory address. Thanks for helping.

x86 Assembly: Segmentation Fault (Core dumped) while trying to reverse print array

There are a lot of mistakes, e.g.:

  • A function can change several registers according to the calling convention
  • scanf and printf have a slightly different syntax for format strings. If you plan to modify the printf output (e.g. with a \n) you have to create another format string.
  • In your loop you forgot to pass any format string to printf, just the address of an integer. (printf needs a format string and takes integers by value.)
  • "%ld" means "long integer". On my sytem this is a Quadword (8 bytes). You are strictly dealing just with one byte.
  • Before calling a function the stack has to be aligned to a multiple of 16. The kernel follows the x86-64 System V ABI and makes sure this is the case on process entry (usually the entry point is called _start). If you push/pop, make sure you don't leave the stack misaligned before a call.
  • _start (the process entry point) is not a function; you can't ret from it. Call glibc's exit function to make sure stdio buffers are flushed, or make a raw _exit system call.
  • section.text is missing a space. It gets parsed as a label name like foo.bar: instead of a directive switching to the .text section. So your code ended up in .data (or perhaps .bss somehow), and segfaulted because those sections are linked into non-executable memory pages.

Take a look at my corrected - now working - program:

DEFAULT REL
; external functions for inputs/outputs printf and scanf/printf
extern printf, fflush
extern scanf

section .data
prompt db "Entrez un entier : ",0
longIntFormat db " %ld",0


section .bss
entier resq 10 ; array of 10 integers


global _start
section .text

_start:
;and rsp, -16 ; Align stack to 16 (the ABI already guarantees this for _start)

mov rbx, 0 ; initialise counter

; fills the array with user input
_getLoop:
; call printf
lea rdi,[prompt]
mov rax,0
call printf wrt ..plt

; call scanf
lea rdi,[longIntFormat]
lea rsi, [entier + rbx * 8] ; array + Index
mov rax,0
call scanf wrt ..plt
inc rbx ; inc. Index/counter
cmp rbx, 10
jl _getLoop ; While counter is less than 10 (size of array)

mov rbx, 9 ; counter set to 10

; print the array in reverse using the counter as Index
_printLoop:

; call printf
lea rdi,[longIntFormat]
mov rsi, [entier + rbx*8] ; rdi = [array + Index]
mov rax,0
call printf wrt ..plt
dec rbx
cmp rbx, 0 ; compare counter with 0
jge _printLoop

xor edi, edi ; RDI=0: all streams
call fflush wrt ..plt

mov rax,60 ; SYS_EXIT
mov rdi,0 ; Exitcode: RDI=0
syscall ; Call Linux64

Why is the segfault address NULL when accessing memory that has any of the 16 most significant bits set?

It's the difference between canonical and non-canonical addresses, coming from the fact that the x86-64 doesn't have a full 64-bit virtual address space. Your second example is a non-canonical address as it isn't a sign-extended 48-bit value (you apparently don't have the 5-level page table extension on your machine or it would be 57 bits); such addresses can never resolve to a physical memory location.

Invalid accesses to canonical addresses generate a page fault (#PF), for which the CPU provides the faulting address to the kernel (in the CR2 register), and the kernel passes it along to userspace in the si_addr field of struct siginfo as you see. But accesses to non-canonical addresses are always invalid and the CPU raises a general protection exception (#GP), or in rare cases, a stack fault (#SS). The designers of the x86 architecture chose, in their infinite wisdom, not to provide the faulting address to software in case of a #GP or #SS exception, so the kernel doesn't get it and neither do you.

If you really need the address, your only choice is to decode the instruction that caused the exception, and inspect the contents of registers as needed to work out what it was trying to do.


I presume this decision was because the kernel really needs the address in case of a page fault. An access to a not-present page may be a memory violation that should kill the process; or, for instance, it may simply be a page that has been swapped out from physical memory. In the latter case, the kernel uses the fault address to find the appropriate page on disk and load it back into physical memory. Then it updates the page tables and returns from the exception handler to restart the faulting instruction, and the program can continue.

However, a general protection fault is typically unrecoverable, and the process will have to be killed, or at least signaled so it can try to clean up. In this case there is nothing actionable to be done with the faulting address, and I guess the architecture designers didn't think its potential value for debugging was worth the effort of having the CPU save it. Anyway, many possible causes of #GP don't arise from a memory access at all (e.g. trying to read or write control registers from unprivileged mode), in which case there is no faulting address.

Why do I get a segmentation fault when attempting to execute an instruction writing to memory when the debugger can manually write to the same memory?

Why does it fail when I've just verified I can write there?

The program can't write there, only GDB can.

The address you are trying to write to is in the .text section, which is normally mmaped with PROT_READ|PROT_EXEC and without PROT_WRITE.

However, GDB (or any process that is ptraceing this one), is allowed to write to such mappings. That is necessary for GDB to be able to insert breakpoints (which often requires re-writing program instructions).



Related Topics



Leave a reply



Submit