C语言的内联汇编和底层操作

0. start函数的内联汇编和底层操作

看到了这个视频,用及其底层的办法重写了sleep,我觉得这种靠近底层的C语言十分有意思,花了时间来学了一下这个东西

点击查看视频,不能内嵌我就没辙了,不过手机UI适合点这个

<iframe src="//player.bilibili.com/player.html?isOutside=true&aid=115687276152800&bvid=BV1gu2DBGEpT&cid=34606156579&p=1" scrolling="no" border="0" frameborder="no" framespacing="0" allowfullscreen="true">\

https://www.bilibili.com/video/BV1gu2DBGEpT/
防止懒得去下方的GitHub仓库看别人的源码,你可以点这里查看它
#define SYS_write 1
#define SYS_nanosleep 35 
#define SYS_exit 60


typedef struct timespec {
    long tv_sec;
    long tv_nsec;
} timespec;

long syscall1(long number, long arg1) {
    long result;

    __asm__ __volatile__(
        "syscall"
        : "=a" (result)
        : "a" (number), "D" (arg1)
        : "rcx", "r11", "memory"
    );

    return result;
}


long syscall2(long number, long arg1, long arg2) {
    long result;

    __asm__ __volatile__(
        "syscall"
        : "=a" (result)
        : "a" (number), "D" (arg1), "S" (arg2)
        : "rcx", "r11", "memory"
    );

    return result;
}


long syscall3(long number, long arg1, long arg2, long arg3) {
    long result;

    __asm__ __volatile__(
        "syscall"
        : "=a" (result)
        : "a" (number), "D" (arg1), "S" (arg2), "d" (arg3)
        : "rcx", "r11", "memory"
    );

    return result;
}


int parse_int(const char *raw_int) {
    int result = 0;
    const char *cursor = raw_int;

    while (*cursor >= '0' && *cursor <= '9') {
        result = result * 10 + (*cursor - '0');
        cursor++;
    }

    return result;
}


long unsigned strlen(const char *string) {
    const char *cursor = string;
    while (*cursor) {
        cursor++;
    }

    long unsigned result = cursor - string;
    return result;
}


void print(const char *string) {
    syscall3(SYS_write, 1, (long)string, strlen(string));
}


void sleep(long seconds) {
    timespec duration = {0};
    duration.tv_sec = seconds;

    syscall2(SYS_nanosleep, (long)(&duration), 0);
}


int main(int argc, char *argv[]) {
    if (argc != 2) {
        print("Usage: sleep NUMBER\nPause for NUMBER seconds\n");
        return 1;
    }

    char *raw_seconds = argv[1];
    long seconds = parse_int(raw_seconds);

    print("Sleeping for ");
    print(raw_seconds);
    print(" seconds\n");
    sleep(seconds);

    return 0;
}

#ifdef __cplusplus  
extern "C" {
#endif

 void exit(int code) {
    syscall1(SYS_exit, code);

    for(;;) {}
}


__attribute__((naked)) void _start() {
    __asm__ __volatile__(
        "xor %ebp, %ebp\n"
        "mov (%rsp), %rdi\n"
        "lea 8(%rsp), %rsi\n"
        "and $-16, %rsp\n"
        "call main\n"
        "mov %rax, %rdi\n"
        "call exit\n"
    );
}

#1. ifdef __cplusplus
}
#endif

1.1 asm__volatile__("mov %%rsp, %0" : "=r"(rsp));

1.2 (long *)rsp (强制类型转换):

rsp 本身只是一个长整型数字(比如 0x7fffffffe1a0)

编译器不知道这个数字代表什么
再用*解引用

总的来说,就是通过数值到地址,再用这个地址来修改值

long argc =*(long *) rsp

1.3 对argv的操作

我们通过+8来越过栈顶,到达argv[0]
argv[0]:存储当前执行程序的名称
argv[n-1]: 存储指向第n-1个参数的指针
argv[n]: NULL

还是不明白?点这里 假设你的程序名为 test,你在终端输入: ./test abc 123

那么在程序运行时:

argc = 3

argv[0] = "./test"
argv[1] = "abc"
argv[2] = "123"

1.4 (char **)

告诉编译器,“从这里开始,后面是一连串的指针,每个指针都指向一个字符串(参数本身)”

2. 为什么不写long *rsp = ???;

因为C语言没有语法能写“当前栈指针”
就是说,C语言对底层的抽象做不到
所以我们必须用asm__volatile__(大部分时间是这个函数

但是汇编可以

mov 其他地方, rsp
把地址拷贝到其他地方

3. 编译器把rsp指向开辟的垃圾区域导致出问题

进入 _start 时,栈顶本来存的是 argc。但编译器为了管理函数局部变量,先执行了 push 和 sub(管理内存,使用修饰词让编译器不要乱加之后,需要自己重写这些函数)

所以项目使用__attribute__((naked))修饰函数,相当于让编译器不要干任何多余的事情

可以随便学点汇编?
寄存器:前面加 %(如 %rax)。
立即数(常数):前面加 \$(如 $8)。
lea 8(%rsp), %rsi
含义:计算 rsp + 8 的结果,然后把这个结果数字给 rsi

其他的大概能猜到?我觉得标志位之类的都不用管?
不过有一点挺有意思的,就是lea 8(%rsp), %rsi相比mov 8(%rsp), %rsi,少了一个访问内存的操作,后者则是最后让rsi变成了那个存着的值,简单说就是先算后找

写到这里吧我还要期末大复习的

点此查看项目源码

https://github.com/valignatev/sleep-from-scratch/blob/master/sleep.c

posted @ 2026-01-15 23:53  气温骤降  阅读(2)  评论(0)    收藏  举报