Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Linux 64 command line parameters in Assembly

This description is valid for Linux 32 bit: When a Linux program begins, all pointers to command-line arguments are stored on the stack. The number of arguments is stored at 0(%ebp), the name of the program is stored at 4(%ebp), and the arguments are stored from 8(%ebp).

I need the same information for 64 bit.

Edit: I have working code sample which shows how to use argc, argv[0] and argv[1]: http://cubbi.com/fibonacci/asm.html

.globl _start
_start:
    popq    %rcx        # this is argc, must be 2 for one argument
    cmpq    $2,%rcx
    jne     usage_exit
    addq    $8,%rsp     # skip argv[0]
    popq    %rsi        # get argv[1]
    call ...
...
}

It looks like parameters are on the stack. Since this code is not clear, I ask this question. My guess that I can keep rsp in rbp, and then access these parameters using 0(%rbp), 8(%rbp), 16(%rbp) etc. It this correct?

like image 292
Alex F Avatar asked Sep 10 '10 08:09

Alex F


2 Answers

Despite the accepted answer being more than sufficient, I would like to give an explicit answer, as there are some other answers which might confuse.

Most important (for more information see examples below): in x86-64 the command line arguments are passed via stack:

(%rsp) -> number of arguments
8(%rsp) -> address of the name of the executable
16(%rsp) -> address of the first command line argument (if exists)
... so on ...

It is different from the function parameter passing in x86-64, which uses %rdi, %rsi and so on.

One more thing: one should not deduce the behavior from reverse engineering of the C main-function. C runtime provides the entry point _start, wraps the command line arguments and calls main as a common function. To see it, let's consider the following example.

No C runtime/GCC with -nostdlib

Let's check this simple x86-64 assembler program, which do nothing but returns 42:

.section .text
.globl _start
_start:   
    movq $60, %rax #60 -> exit
    movq $42, %rdi #return 42
    syscall #run kernel 

We build it with:

as --64 exit64.s -o exit64.o
ld -m elf_x86_64 exit64.o -o exit64

or with

gcc -nostdlib exit64.s -o exit64

run in gdb with

./exit64 first second third

and stop at the breakpoint at _start. Let's check the registers:

(gdb) info registers
...
rsi            0x0  0
rdi            0x0  0
...

Nothing there. What about the stack?

(gdb) x/5g $sp
0x7fffffffde40: 4   140737488347650
0x7fffffffde50: 140737488347711 140737488347717
0x7fffffffde60: 140737488347724

So the first element on the stack is 4 - the expected argc. The next 4 values look a lot like pointers. Let's look at the second pointer:

(gdb) print (char[5])*(140737488347711)
$1 = "first"

As expected it is the first command line argument.

So there is experimental evidence, that the command line arguments are passed via stack in x86-64. However only by reading the ABI (as the accepted answer suggested) we can be sure, that this is really the case.

With C runtime

We have to change the program slightly, renaming _start into main, because the entry point _start is provided by the C runtime.

.section .text
.globl main
main:   
    movq $60, %rax #60 -> exit
    movq $42, %rdi #return 42
    syscall #run kernel 

We build it with (C runtime is used per default):

gcc exit64gcc.s -o exit64gcc

run in gdb with

./exit64gcc first second third

and stop at the breakpoint at main. What is at the stack?

(gdb) x/5g $sp
0x7fffffffdd58: 0x00007ffff7a36f45  0x0000000000000000
0x7fffffffdd68: 0x00007fffffffde38  0x0000000400000000
0x7fffffffdd78: 0x00000000004004ed

It does not look familiar. And registers?

(gdb) info registers
...
rsi            0x7fffffffde38   140737488346680
rdi            0x4  4
...

We can see that rdi contains the argc value. But if we now inspect the pointer in rsi strange things happen:

(gdb) print (char[5])*($rsi)
$1 =  "\211\307???"

But wait, the second argument of the main function in C is not char *, but char ** also:

(gdb) print (unsigned long long [4])*($rsi)
$8 = {140737488347644, 140737488347708, 140737488347714, 140737488347721}
(gdb) print (char[5])*(140737488347708)
$9 = "first"

And now we found our arguments, which are passed via registers as it would be for a normal function in x86-64.

Conclusion: As we can see, the is a difference concerning passing of command line arguments between code using C runtime and code which doesn't.

like image 84
ead Avatar answered Nov 01 '22 09:11

ead


It looks like section 3.4 Process Initialization, and specifically figure 3.9, in the already mentioned System V AMD64 ABI describes precisely what you want to know.

like image 39
rkhayrov Avatar answered Nov 01 '22 07:11

rkhayrov