简体   繁体   中英

x86-64 linux assembly. using write on argv doesn't work because of EFAULT? “Bad address”

I've very recently decided to give x86-64 assembly a go. I've run into a problem with displaying argv

Yes the code I've written is bad, and it makes assumptions and doesn't check for errors, I know, but I really don't think that's the cause of this problem.

Here's my programme boiled down to its core.

        .globl  _start
        .text
main:
_start:
        movl    $10,%edx          # No. of chars to write
        movq    16(%rsp),%rcx     # argv[1]
        movl    $1,%ebx           # stdout
        movl    $4,%eax           # write
        int     $0x80

        movl    $0,%ebx
        movl    $1,%eax
        int     $0x80

I run my programme ./myprog helloworldddddddddd

So argv[0] =./myprog and argv[1] = helloworlddddddddddd or whatever I typed

My programme should write the first ten characters in argv[1] to stdout.

Except it doesn't work. Write is returning -14, which is error EFAULT? Which means bad address.

So I wrote this instead

        .globl  _start
        .text
main:
_start:
        movq    16(%rsp),%rax
        movq    $bob,%rbx
        subq    %rcx,%rcx
.Lloop:
        movb    (%rax,%rcx),%dl
        movb    %dl,(%rbx,%rcx)
        cmpb    $0,%dl
        je      .Ldone
        addq    $1,%rcx
        jmp     .Lloop

.Ldone: movl    $10,%edx
        movq    $bob,%rcx
        movl    $1,%ebx
        movl    $4,%eax
        int     $0x80

        movl    $0,%ebx
        movl    $1,%eax
        int     $0x80

        .comm   bob,50

It copies argv[1] to an area of memory I've called bob, and then tries to write this copy to stdout. I'm sure it is abysmal x86, but it works. If I compile and run the programme, it outputs whatever was in argv[1]

Finally I wrote this in C

#include <unistd.h>

int main(int argc, char **argv) {
        write(1,argv[1],10);

        return 0;
}

Which is more like my first programme than my second, but also works. By this stage I am totally baffled.

So it seems to me that the "write" system call isn't allowed to read my programme's argv array, yet it can read a copy. Oh, and if I just write it in C, it works. This seems totally bizarre. Can anyone tell me what's going on, and why?


Edit:

It's been pointed out that I was using mixed 32 bit and 64 bit code. so I changed to 100% 64bit.

First programme:

    .globl  _start
    .text
main:
_start:
    movl    $10,%edx                # No. of chars to write
    movl    16(%rsp),%esi           # argv[1]
    movl    $1,%edi                 # stdout
    movl    $1,%eax                 # write
    syscall

    movl    $0,%edi
    movl    $60,%eax
    syscall

Second programme:

    .globl  _start
    .text
main:
_start:
    movq    16(%rsp),%rax
    movq    $bob,%rbx
    subq    %rcx,%rcx
.Lloop:
    movb    (%rax,%rcx),%dl
    movb    %dl,(%rbx,%rcx)
    cmpb    $0,%dl
    je      .Ldone
    addq    $1,%rcx
    jmp     .Lloop

.Ldone: movl    $10,%edx                # No. of chars to write
    movq    $bob,%rsi               # buffer
    movl    $1,%edi                 # stdout
    movl    $1,%eax                 # write
    syscall

    movl    $0,%edi                 # return 0
    movl    $60,%eax                # exit
    syscall

    .comm   bob,50

Still the same error, the first programme still fails to work even though it's now 64bit, and the second programme still works, even though it too is now 64bit

Strace for 64bit version of first programme. (The one that doesn't work):

execve("./Myprog", ["./Myprog", "bananablahblah"], [/* 46 vars */]) = 0
write(1, 0x1491f543, 10)                = -1 EFAULT (Bad address)
_exit(0)                                = ?
+++ exited with 0 +++

You are making three major errors here:

  1. You are confusing main with _start . They are not synonymous — the standard C library implementation of _start does some important initialization before calling main . You probably don't want to try to reimplement _start — don't define it in your executable; link against libc to get it.

  2. You are trying to use 32-bit system calls ( int $0x80 ) in a 64-bit executable. This does not work correctly; in particular, it cannot read memory that exists above the 4GB boundary, including on your stack! You must use the syscall instruction to make 64-bit system calls. Keep in mind, however, that it uses a slightly different calling convention, and uses different call numbers!

  3. You are using 32-bit instructions and registers in some locations where you need the 64-bit equivalents. This sometimes works for low pointers and values, but truncates some other values. Make a habit of always using 64-bit instructions and registers in 64-bit code unless you specifically need something smaller (eg, an 8-bit register for a char ).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM