You have several bugs:
You push two 4-byte arguments onto the stack for
printf. In SysV calling conventions,
printfwill leave them there, and so it is your responsibility to adjust the stack afterwards to “remove” them. Remember that
retwill look for a return address at the top of the stack; as your code stands, what will be there is the character value from
eaxthat you pushed. That’s not a valid address, so trying to return there causes a segfault. You can remove those arguments by
popping twice, or more efficiently by simply adding
esp, thus moving the stack pointer back to where it was.
Current versions of the i386 SysV ABI require the stack to be aligned to 16 bytes just before
calling any function. Thinking about the fact that
callitself pushes 4 bytes on the stack as the return address, as does every
pushinstruction, you can work out the necessary adjustments needed for your calls to
printf, and add or subtract from
espas appropriate. (Technically you could avoid aligning the stack before calling
some_procand just fix it up before
printf, but this is too easy to screw up.) Some 32-bit libraries may be compiled in such a way that this requirement is not enforced, but 64-bit code definitely needs it, so it is a good habit to comply.
esiis a callee-saved register according to i386 SysV ABI calling conventions (memorize these!). If you want to modify it, you have to save the previous contents and restore them before returning (e.g.
push esiat the top of the function and
pop esiat the end). Or choose a caller-saved register such as
ecxinstead. However, as noted below, you don’t really need to use a register for the address of
mov eax, [esi]is a 32-bit load because
eaxis a 32-bit register. So this will load
eaxwith the 4 bytes from location
str_1, which will result in it containing the value
t h r eas a little-endian integer). This may not actually cause a problem since
printfis supposed to convert its
intargument back to
unsigned charfor printing, so you should only get the low byte
0x74 = 't', but it is still weird, and could break if your string was very short and adjacent to an unmapped page.
Safer would be
mov al, [esi]which just loads one byte into
al, which is the low byte of
eax, but whatever garbage is in the high 3 bytes will stay there. You could zero out
xor eax, eax, but you can also kill two birds with one stone with the
movzxinstruction, which zero-extends a smaller operand into a larger one:
movzx eax, byte [esi].
Of course, putting the address into
esifirst is redundant, since the address can be specified as an immediate:
mov al, [str_1]or
movzx eax, byte [str_1]. This then avoids the need to save/restore
mainis expected to return an exit code, and return values always go in
eaxwould contain your characters or maybe the return value from
printf, depending where your push/pops end up. Any of those will be a weird nonzero exit code and your shell will think the program encountered an error. So zero out
eaxbefore returning from
main, to indicate success.
argv_stris a strange name for a string that has nothing to do with
I would modify your program as follows:
; nasm -f test.asm && gcc -m32 -o test test.asm.o section .text global main extern printf some_proc: sub esp, 4 ; 8 more bytes pushed before call to printf movzx eax, byte [str_1] push eax push argv_str call printf add esp, 12 ret main: sub esp, 12 call some_proc xor eax, eax add esp, 12 ret section .data str_1 db `three` argv_str db `%c\n`
CLICK HERE to find out more related problems solutions.