Skip to content
Advertisement

Scanf a char pointer in Assembly

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 PUSHes 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
User contributions licensed under: CC BY-SA
5 People found this is helpful
Advertisement