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: %sn" .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: %sn") 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
Advertisement
Answer
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: %sn" .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: %sn") addl $8, %esp ##### 16 was wrong. Only 2 DWORD à 4 bytes were pushed leave ret