1

I am trying to write my own _start function using inline assembly. But when I try to read argc and argv from stack (%rsp and %rsp + 8) I get wrong values. I don't know what I am doing wrong.

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <syscall.h>

int main(int argc, char *argv[]) {
    printf("%d\n", argc);
    printf("%s\n", argv[0]);
    printf("got here\n");
    return 0;
}

void _start() {
    __asm__(
    "xor %rbp, %rbp;"
    "movl (%rsp), %edi;"
    "lea 8(%rsp), %rsi;"
    "xor %rax, %rax;"
    "call main"
...

Terminal:

$ gcc test.c -nostartfiles
$ ./a.out one two three
0
Segmentation fault (core dumped)
$

Any idea where my fault could be ? I am using a Ubuntu 20.04 VM

Lantanar
  • 35
  • 5
  • Have you tried debugging it and getting the PC at which seg fault happens? – scrutari Dec 06 '20 at 15:34
  • Seg fault happens because the adress in rsi (argv) is a random adress instead of what should be a pointer to ["one","two","three"], but in the System V abi it says that this pointer should be at $rsp + 8 – Lantanar Dec 06 '20 at 15:41
  • If you are using `-nostartfiles`, then most likely the code which sets up processing of command line arguments is not called at all. – scrutari Dec 06 '20 at 15:50
  • 1
    @scrutari: You're thinking of Windows. On Unix systems (and GNU/Linux), command args aren't a flat string, they're already separated (as passed to `execve` by the shell or whatever) with `argc` and `argv[]` on the stack on entry to user-space. This looks correct for a minimal `_start:` if the OP didn't put it inside a non-`naked` C function that will use the stack before the asm statement. [How Get arguments value using inline assembly in C without Glibc?](https://stackoverflow.com/q/50260855). Never use GNU C Basic asm statements in a non-naked function. – Peter Cordes Dec 06 '20 at 15:53

1 Answers1

4

This looks correct for a minimal _start: but you put it inside a non-naked C function. Compiler-generated code will run, e.g. push %rbp / mov %rsp, %rbp, before execution enters before the asm statement. To see this, look at gcc -S output, or single-step in a debugger such as GDB.

Put your asm statement at global scope (like in How Get arguments value using inline assembly in C without Glibc?) or use __attribute__((naked)) on your _start(). Note that _start isn't really a function

As a rule, never use GNU C Basic asm statements in a non-naked function. Although you might get this to work with -O3 because that would imply -fomit-frame-pointer so the stack would still be pointing at argc and argv when your code ran.

A dynamically linked executable on GNU/Linux will run libc startup code from dynamic linker hooks, so you actually can use printf from _start without manually calling those init functions. Unlike if this was statically linked.

However, your main tries to return to your _start, but you don't show _start calling exit. You should call exit instead of making an _exit system call directly, to make sure stdio buffers get flushed even if output is redirected to a file (making stdout full buffered). Falling off the end of _start would be bad, crashing or getting into an infinite loop depending on what execution falls in to.

Peter Cordes
  • 328,167
  • 45
  • 605
  • 847