So I have a task to do, which requires from me to scanf
a char*
in assembly. I tried this code:
.data
INPUT_STRING: .string "Give me a string: "
SCANF_STRING: .string "%s"
PRINTF_STRING: .string "String: %s\n"
.text
.globl main
.type main, @function
main:
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %ecx
subl $32, %esp
pushl $INPUT_STRING
call printf #printf("Give me a string: ")
addl $4, %esp
pushl -12(%ebp) # char*
pushl $SCANF_STRING # "%s"
call scanf scanf("%s", char*)
addl $8, %esp
pushl -12(%ebp)
pushl PRINTF_STRING
call printf #printf("String: %s\n")
addl $16, %esp
movl -4(%ebp), %ecx
xorl %eax, %eax
leave
leal -4(%ecx), %esp
ret
It writes down first printf correctly, then it waits for input (so scanf
works), but then when I enter anything -> Segmentation fault
.
I know, that the char*
should be somehow initialized, but how can I do it from the assembly level?
I am compiling it on Manjaro 64 bit, with gcc -m32
GCC's stack-alignment code on entry to main is over-complicated:
leal 4(%esp), %ecx
andl $-16, %esp
pushl -4(%ecx)
pushl %ebp
movl %esp, %ebp
pushl %ecx
subl $32, %esp
...
leave
leal -4(%ecx), %esp
ret
Do it so:
pushl %ebp
movl %esp, %ebp
subl $32, %esp # Space for 32 local bytes
andl $-16, %esp # Alignment by 16
...
leave
ret
The version of the i386 System V ABI used on modern Linux does guarantee/require 16-byte stack alignment before a call
, so you could have re-aligned with 3 pushes (including the push %ebp
) instead of an and
. Unlike x86-64, most i386 library functions don't get compiled to use movaps
or movdqa
16-byte aligned load/store on locals in their stack space, so you can often get away with unaligning the stack like you're doing with PUSH
es before scanf. (ESP % 16 == 0
when you call printf
the first time, though; that's correct.)
You want to use 12 bytes of the local stack frame for the string. scanf
needs the start address of those 12 bytes. The address for that area isn't known at compile time. A -12(%ebp)
gives you the value at this address, not the address itself. LEA
is the instruction to calculate an address. So you have to insert this instruction to get the address at run time and to pass it to the C function:
leal -12(%ebp), %eax
pushl %eax # char*
And this is the working example (minor mistakes also corrected):
.data
INPUT_STRING: .string "Give me a string: "
SCANF_STRING: .string "%11s" ##### Accept only 11 characters (-1 because terminating null)
PRINTF_STRING: .string "String: %s\n"
.text
.globl main
.type main, @function
main:
pushl %ebp
movl %esp, %ebp
subl $32, %esp
mov $32, %ecx
mov %esp, %edi
mov $88, %al
rep stosb
pushl $INPUT_STRING
call printf # printf("Give me a string: ")
addl $4, %esp
leal -12(%ebp), %eax
pushl %eax # char*
pushl $SCANF_STRING # "%s"
call scanf # scanf("%s", char*)
addl $8, %esp
leal -12(%ebp), %eax
pushl %eax # char*
pushl $PRINTF_STRING ##### '$' was missing
call printf # printf("String: %s\n")
addl $8, %esp ##### 16 was wrong. Only 2 DWORD à 4 bytes were pushed
leave
ret