【问题标题】:How can I read arguments in _start function?如何读取 _start 函数中的参数?
【发布时间】:2023-04-01 08:41:01
【问题描述】:

我正在 linux 64 位中构建没有 glibc 的简单应用程序。 但我不知道如何获得论点。

我搜索了一下,发现 RDI 是 argc,RSI 是 argv。但它没有用。

我在 _start 函数开始使用 gdb 时看到了寄存器,但 RDI 和 RSI 都是 0x0。 我还用最简单的组装应用程序进行了测试,但结果是一样的。 RDI 和 RSI 为 0x0。我相信即使我没有向程序传递任何参数,argc 也不应该是 0x0。

_start:
jmp $

这是我尝试过的 C 代码:

//Print Hello world WITHOUT standard library
//ONLY WITH SYSTEM CALL

#define ReadRdi(To) asm("movq %%rdi,%0" : "=r"(To));
#define ReadRsi(To) asm("movq %%rsi,%0" : "=r"(To));

void __Print(const char *str);
void __Exit();

void Test(const char *a){
    long aL;
    ReadRdi(aL);
    char *aC = (int) aL;
    __Print("Test argument: ");
    __Print(aC);
    __Print("\n");
}

void _start() {
    long argcL;
    long argvL;
    ReadRdi(argcL);
    ReadRsi(argvL);
    int argc = (int) argcL;
    char **argv = (char **) argvL;
    __Print("Arguments: ");
    for(int i = 0; i < argc; i ++) {
        __Print(argv[i]);
        __Print(", ");
    }
    __Print("\n");
    Test("Hello, world!");
    __Exit();
}

结果是:

Arguments:
Test argument: Hello, world!

我使用 gdb 检查了堆栈(RBP~RSP 中的内存值),但似乎什么都没有。

我尝试改变

void _start() {
    long argcL;
    long argvL;
    ReadRdi(argcL);
    ReadRsi(argvL);
    int argc = (int) argcL;
    char **argv = (char **) argvL;
    __Print("Arguments: ");

void _start(int argc, char **argv) {
    __Print("Arguments: ");

但我仍然看不到任何参数输出。

__Print 打印消息(使用 sys_write 系统调用),__Exit 退出程序(使用 sys_exit 系统调用)。

打印.asm:

section     .text
global      __Print
__Print:
    mov rdx, 0
    push rdi
    jmp .count
.count:
    add rdx, 1
    add rdi, 1
    cmp byte[rdi], 0
    jne .count
.print:
    pop rdi
    mov     rcx, rdi                             ;message to write
    mov     rbx, 1                               ;file descriptor (stdout)
    mov     rax, 4                               ;system call number (sys_write)
    int     0x80                                ;call kernel
    ret

退出.asm:

section     .text
global      __Exit
__Exit:
    mov rax,1                               ;system call number (sys_exit)
    int 0x80

添加:我用这个命令链接:

gcc -o sysHello Exit.o Print.o sysHello.o -nostdlib -nodefaultlibs -g

【问题讨论】:

  • 请注意,rdi, rsi 约定不适用于_start(原始进程入口点)。此外,64 位系统调用与 32 位不同,因此所有这些都是错误的。此外,当您读取内联汇编中的寄存器时,编译器可能已经更改了它们,所以不要依赖这种行为。
  • 标识符以_ 开头,后跟一个大写字母或另一个下划线,保留用于实现。您不得在应用程序代码中使用它们。
  • 如果你覆盖每个程序用于初始化的内部_start函数,你不会破坏控制流吗?有可能这样吗?

标签: c linux assembly 64-bit


【解决方案1】:

最简单的方法是在 asm 中编写 _start,并让它使用标准调用约定调用您的 C 函数。

请参阅 wiki 以获取指向 ABI 文档的链接,该文档描述了在流程启动时在哪里可以找到所有内容。 (或使用 Ian 评论中的链接)。

_start 编写为C 函数需要内联汇编,因为没有标准的方法可以告诉编译器argc 是返回地址通常所在的位置。因此,直接在 asm 中编写它并在将 args 放入寄存器以进行正常调用约定之后将其 call main 更容易。

我用来测试性能计数器的 asm 程序片段:

cmp dword [rsp], 2
jg  addrmode_lat_3comp  ; argc > 2  ; $(seq 2)
jge addrmode_lat_1comp  ; argc >= 2 ; $(seq 1)
jmp loadlat_1comp       ; argc < 2  ; $(seq 0)

通过测试argc,它会跳转到三个循环之一,具体取决于我是使用 2、1 还是没有 args 运行它。它使用 NASM 语法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-05-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-12-31
    相关资源
    最近更新 更多