Search code examples
linuxassemblyatt

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: %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


Solution

  • 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: %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