I am learning AT&T x86 assembly language. I am trying to write an assembly program which takes an integer n, and then return the result (n/2+n/3+n/4). Here is what I have done:
.text
.global _start
_start:
pushl $24
call profit
movl %eax, %ebx
movl $1, %eax
int $0x80
profit:
popl %ebx
popl %eax
mov $0, %esi
movl $4, %ebp
div %ebp
addl %eax, %esi
movl %ecx, %eax
movl $3, %ebp
div %ebp
addl %eax, %esi
movl %ecx, %eax
movl $2, %ebp
div %ebp
addl %eax, %esi
movl %esi, %eax
cmpl %ecx, %esi
jg end
pushl %ebx
ret
end:
mov %ecx, %eax
ret
The problem is I am getting segmentation fault. Where is the problem?
Claudio
10.6k4 gold badges31 silver badges71 bronze badges
asked Sep 26, 2012 at 15:06
4
I think the code fails here:
_start:
pushl $24
call profit
movl %eax, %ebx
movl $1, %eax
int $0x80
profit:
popl %ebx
popl %eax
So, you push $24
(4 bytes) and then call profit
, which pushes eip
and jumps to profit
. Then you pop the value of eip
into ebx
and the value $24
into eax
.
Then, in the end, if jg end
branches to end:
, then the stack won’t hold a valid return address and ret
will fail. You probably need pushl %ebx
there too.
cmpl %ecx, %esi
jg end
pushl %ebx
ret
end:
mov %ecx, %eax
; `pushl %ebx` is needed here!
ret
answered Sep 26, 2012 at 15:17
nrznrz
10.4k4 gold badges39 silver badges71 bronze badges
2
You do not appear to be doing function calls correctly. You need to read and understand the x86 ABI (32-bit, 64-bit) particularly the «calling convention» sections.
Also, this is not your immediate problem, but: Don’t write _start
, write main
as if this were a C program. When you start doing something more complicated, you will want the C library to be available, and that means you have to let it initialize itself. Relatedly, do not make your own system calls; call the wrappers in the C library. That insulates you from low-level changes in the kernel interface, ensures that errno
is available, and so on.
answered Sep 26, 2012 at 15:33
zwolzwol
135k36 gold badges248 silver badges357 bronze badges
1
- you use
ecx
without ever explicitly initializing it (I’m not sure if Linux will guarantee the state ofecx
when the process starts — looks like it’s0
in practice if not by rule) - when the program takes the
jg end
jump near the end of the procedure, the return address is no longer on the stack, soret
will transfer control to some garbage address.
answered Sep 26, 2012 at 15:17
Michael BurrMichael Burr
332k50 gold badges530 silver badges756 bronze badges
Your problem is that you pop the return address off of the stack and when you branch to end you don’t restore it. A quick fix is to add push %ebx
there as well.
What you should do is modify your procedure so it uses the calling convention correctly. In Linux, the caller function is expected to clean the arguments from the stack, so your procedure should leave them where they are.
Instead of doing this to get the argument and then restoring the return address later
popl %ebx
popl %eax
You should do this and leave the return address and arguments where they are
movl 4(%esp), %eax
and get rid of the code that pushes the return address back onto the stack. You then should add
subl $4, %esp
after the call to the procedure to remove the argument from the stack. It’s important to follow this convention correctly if you want to be able to call your assembly procedures from other languages.
answered Sep 26, 2012 at 16:21
Dirk HolsoppleDirk Holsopple
8,7111 gold badge23 silver badges37 bronze badges
It looks to me like you have a single pushl before you call profit and then the first thing that profit does is to do two popl instructions. I would expect that this would pop the value you pushed onto the stack as well as the return code so that your ret would not work.
push and pop should be the same number of times.
call pushes the return address onto the stack.
answered Sep 26, 2012 at 15:17
Richard ChambersRichard Chambers
16.5k4 gold badges78 silver badges105 bronze badges
Segmentation faults commonly occur when programs attempt to access memory regions that they are not allowed to access. This article provides an overview of segmentation faults with practical examples. We will discuss how segmentation faults can occur in x86 assembly as well as C along with some debugging techniques.
See the previous article in the series, How to use the ObjDump tool with x86.
What are segmentation faults in x86 assembly?
A segmentation fault occurs when a program attempts to access a memory location that it is not allowed to access, or attempts to access a memory location in a way that is not allowed (for example, attempting to write to a read-only location).
Let us consider the following x86 assembly example.
message db “Welcome to Segmentation Faults! ”
section .text
global _start
_printMessage:
mov eax, 4
mov ebx, 1
mov ecx, message
mov edx, 32
int 0x80
ret
_start:
call _printMessage
As we can notice, the preceding program calls the subroutine _printMessage when it is executed. When we read this program without executing, it looks innocent without any evident problems. Let us assemble and link it using the following commands.
ld print.o -o print -m elf_i386
Now, let us run the program and observe the output.
Welcome to Segmentation Faults! Segmentation fault (core dumped)
As we can notice in the preceding excerpt, there is a segmentation fault when the program is executed.
How to detect segmentation faults in x86 assembly
Segmentation faults always occur during runtime but they can be detected at the code level. The previous sample program that was causing the segmentation faults, is due to lack of an exit routine within the program. So when the program completes executing the code responsible for printing the string, it doesn’t know how to exit and thus lands on some invalid memory address.
Another way to detect segmentation faults is to look for core dumps. Core dumps are usually generated when there is a segmentation fault. Core dumps provide the situation of the program at the time of the crash and thus we will be able to analyze the crash. Core dumps must be enabled on most systems as shown below.
When a segmentation fault occurs, a new core file will be generated as shown below.
Welcome to Segmentation Faults! Segmentation fault (core dumped)
$ ls
core seg seg.nasm seg.o
$
As shown in the proceeding excerpt, there is a new file named core in the current directory.
How to fix segmentation faults x86 assembly
Segmentation faults can occur due to a variety of problems. Fixing a segmentation fault always depends on the root cause of the segmentation fault. Let us go through the same example we used earlier and attempt to fix the segmentation fault. Following is the original x86 assembly program causing a segmentation fault.
message db “Welcome to Segmentation Faults! ”
section .text
global _start
_printMessage:
mov eax, 4
mov ebx, 1
mov ecx, message
mov edx, 32
int 0x80
ret
_start:
call _printMessage
As mentioned earlier, there isn’t an exit routine to gracefully exit this program. So, let us add a call to the exit routine immediately after the control is returned from _printMessage. This looks as follows
message db “Welcome to Segmentation Faults! ”
section .text
global _start
_printMessage:
mov eax, 4
mov ebx, 1
mov ecx, message
mov edx, 32
int 0x80
ret
_exit:
mov eax, 1
mov ebx, 0
int 0x80
_start:
call _printMessage
call _exit
Notice the additional piece of code added in the preceding excerpt. When _printMessage completes execution, the control will be transferred to the caller and call _exit instruction will be executed, which is responsible for gracefully exiting the program without any segmentation faults. To verify, let us assemble and link the program using the following commands.
ld print-exit.o -o print-exit -m elf_i386
Run the binary and we should see the following message without any segmentation fault.
Welcome to Segmentation Faults!
$
As mentioned earlier, the solution to fix a segmentation fault always depends on the root cause.
How to fix segmentation fault in c
Segmentation faults in C programs are often seen due to the fact that C programming offers access to low-level memory. Let us consider the following example written in C language.
{
char *str;
str = “test string”;
*(str+1) = ‘x’;
return 0;
}
The preceding program causes a segmentation fault when it is run. The string variable str in this example stores in read-only part of the data segment and we are attempting to modify read-only memory using the line *(str+1) = ‘x’;
Similarly, segmentation faults can occur when an array out of bound is accessed as shown in the following example.
{
char test[3];
test[4] = ‘A’;
}
This example also leads to a segmentation fault. In addition to it, if the data being passed to the test variable is user-controlled, it can lead to stack-based buffer overflow attacks. Running this program shows the following error due to a security feature called stack cookies.
*** stack smashing detected ***: terminated
Aborted (core dumped)
$
The preceding excerpt shows that the out-of-bound access on an array can also lead to segfaults. Fixing these issues in C programs again falls back to the reason for the Segfault. We should avoid accessing protected memory regions to minimize segfaults.
How to debug segmentation fault
Let us go through our first x86 example that was causing a segfault to get an overview of debugging segmentation faults using gdb. Let us begin by running the program, so we can get the core dump when the segmentation fault occurs.
Welcome to Segmentation Faults! Segmentation fault (core dumped)
$
Now, a core dump should have been generated. Let us load the core dump along with the target executable as shown in the following command. Loading the executable along with the core dump makes the debugging process much easier.
GEF for linux ready, type `gef’ to start, `gef config’ to configure
78 commands loaded for GDB 9.1 using Python engine 3.8
[*] 2 commands could not be loaded, run `gef missing` to know why.
[New LWP 6172]
Core was generated by `./print’.
Program terminated with signal SIGSEGV, Segmentation fault.
#0 0x0804901c in ?? ()
gef➤
As we can notice in the preceding output, the core dump is loaded using GDB and the segmentation fault occurred at the address 0x0804901c. To confirm this, we can check the output of info registers.
eax 0x20 0x20
ecx 0x804a000 0x804a000
edx 0x20 0x20
ebx 0x1 0x1
esp 0xffbe8aa0 0xffbe8aa0
ebp 0x0 0x0
esi 0x0 0x0
edi 0x0 0x0
eip 0x804901c 0x804901c
eflags 0x10202 [ IF RF ]
cs 0x23 0x23
ss 0x2b 0x2b
ds 0x2b 0x2b
es 0x2b 0x2b
fs 0x0 0x0
gs 0x0 0x0
gef➤
As highlighted, the eip register contains the same address. This means, the program attempted to execute the instruction at this address and it has resulted in a segmentation fault. Let us go through the disassembly and understand where this instruction is.
First, let us get the list of functions available and identify which function possibly caused the segfault.
All defined functions:
Non-debugging symbols:
0x08049000 _printMessage
0x08049017 _start
0xf7fae560 __kernel_vsyscall
0xf7fae580 __kernel_sigreturn
0xf7fae590 __kernel_rt_sigreturn
0xf7fae9a0 __vdso_gettimeofday
0xf7faecd0 __vdso_time
0xf7faed10 __vdso_clock_gettime
0xf7faf0c0 __vdso_clock_gettime64
0xf7faf470 __vdso_clock_getres
gef➤
As highlighted in the preceding excerpt, the _printMessage and _start functions’ address ranges are close to the address that caused the segmentation fault. So, let us begin with the disassembly of the function _printMessage.
Dump of assembler code for function _printMessage:
0x08049000 <+0>: mov eax,0x4
0x08049005 <+5>: mov ebx,0x1
0x0804900a <+10>: mov ecx,0x804a000
0x0804900f <+15>: mov edx,0x20
0x08049014 <+20>: int 0x80
0x08049016 <+22>: ret
End of assembler dump.
gef➤
Let us set a breakpoint at ret instruction and run the program. The following command shows how to setup the breakpoint.
Breakpoint 1 at 0x8049016
gef➤
Type run to start the program execution.
0x804900f <_printMessage+15> mov edx, 0x20
0x8049014 <_printMessage+20> int 0x80
→ 0x8049016 <_printMessage+22> ret
↳ 0x804901c add BYTE PTR [eax], al
0x804901e add BYTE PTR [eax], al
0x8049020 add BYTE PTR [eax], al
0x8049022 add BYTE PTR [eax], al
0x8049024 add BYTE PTR [eax], al
0x8049026 add BYTE PTR [eax], al
As we can notice in the preceding excerpt, when the ret instruction gets executed, the control gets passed to the region not controlled by the program code leading to unauthorized memory access and thus a segmentation fault.
Conclusion
This article has outlined some basic concepts around segmentation faults in x86 assembly and how one can use them for debugging programs. We have seen various simple examples to better understand the concepts. We briefly discussed core dumps, which can help us to detect and analyze program crashes.
See the next article in this series, How to control the flow of a program in x86 assembly.
Sources
- https://www.geeksforgeeks.org/core-dump-segmentation-fault-c-cpp/
- http://www.brendangregg.com/blog/2016-08-09/gdb-example-ncurses.html
- https://embeddedbits.org/linux-core-dump-analysis/
The problem lies here:
func1:
pushl ans1
ret
Your «answer» from the function cannot be in the stack. If you push a value to the stack, the ret
instruction will take that value as the address to jump to, causing the segfault.
You have to choose a register to hold the return value (usually EAX in IA32 architecture) and do something like this:
func1:
mov ans1, %eax
ret
(func2
has of course the same problem)
Your application, after calling either func1
or func2
, will return the value, reading it from EAX
, not from the stack:
movl %eax, %edx # save value returned from function into EDX
movl $1, %eax
movl %edx, %ebx # set result (%edx) as exit code
int $0x80
Пишу лабораторку по ассемблеру
Суть программы такая:
Вот код
SECTION .data
ask: DB 'Please, input string',10
askLen: EQU $-ask
SECTION .bss
buf1 RESB 80
buf2 RESB 80
SECTION .text
GLOBAL _start
convert:
xor ecx,ecx
xor ebx,ebx
mov eax,[buf1]
mov bl,2
.divide:
xor edx,edx
div ebx
add dl,'0'
push dx
inc ecx
cmp eax,0
jnz .divide
.reverse:
pop ax
stosb
loop .reverse
ret
bit:
mov ecx,0
lpl:
mov edi,esi
inc esi
mov al,[edi]
cmp al,10
je end
sub al,'0'
cmp al,0
JB lpl
cmp al,9
JA lpl
mov cl,al
mov edx,1
shl edx,cl
or [buf1],edx
jmp lpl
end:
ret
_start:
mov eax,4
mov ebx,1
mov ecx,ask
mov edx,askLen
int 80h
mov eax,3
mov ebx,1
mov ecx,buf2
mov edx,10
int 80h
call convert
mov eax,4
mov ebx,1
mov ecx,buf1
mov edx,10
int 80h
mov eax,1
mov ebx,0
int 80h
При запуске происходит это:
Пожалуйста, подскажите, в чем ошибка
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
section .text global _start NumToStr: mov ebx,10 xor ecx,ecx strloop: xor edx,edx div ebx push edx inc ecx test eax,eax jnz strloop outloop: pop eax push ecx add al,'0' mov [val],al mov eax,4 mov ebx,1 mov ecx,val mov edx,1 int 80h pop ecx loop outloop ret toInt: mov eax, dword [result] rcl eax,1 mov ebx, eax mov edx, 4278190080 and eax, edx shr eax, 24 sub eax, 7fh mov edx, eax mov eax, ebx rcl eax, 8 mov ebx, eax mov ecx, 1fh sub ecx, edx mov edx,00000000h cmp ecx, 0 je loop2 shr eax,1 or eax,80000000h loop1: shr eax, 1 sub ecx, 1 add edx, 1 cmp ecx,0 ja loop1 loop2: mov [result], eax sign: mov eax, dword [result] and eax, 80000000h cmp eax, 80000000h je putsign mov dword [result], eax ret putsign: mov dword [result], eax neg dword [result] ret _start: mov eax, [x] mov ebx, [y] imul eax,ebx mov [multiply], eax mov ecx, [multiply] mov edx, [z] mov eax, ecx imul edx mov eax, ecx sar eax, 31 sub edx, eax mov eax, edx mov [result], eax call toInt mov eax, dword [result] call NumToStr xor eax,eax xor ecx, ecx xor edx, edx xor ebx, ebx mov eax, 1 int 0x80 section .data x dd 25 y dd 37 z dd 3 multiply dd 0 division dd 0 result dd 0 |