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">\
防止懒得去下方的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

浙公网安备 33010602011771号