- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我遇到了一个没有 libc 的最小 HTTP 服务器:https://github.com/Francesco149/nolibc-httpd
我可以看到定义了基本的字符串处理函数,导致 write
系统调用:
#define fprint(fd, s) write(fd, s, strlen(s))
#define fprintn(fd, s, n) write(fd, s, n)
#define fprintl(fd, s) fprintn(fd, s, sizeof(s) - 1)
#define fprintln(fd, s) fprintl(fd, s "\n")
#define print(s) fprint(1, s)
#define printn(s, n) fprintn(1, s, n)
#define printl(s) fprintl(1, s)
#define println(s) fprintln(1, s)
基本的系统调用在 C 文件中声明:
size_t read(int fd, void *buf, size_t nbyte);
ssize_t write(int fd, const void *buf, size_t nbyte);
int open(const char *path, int flags);
int close(int fd);
int socket(int domain, int type, int protocol);
int accept(int socket, sockaddr_in_t *restrict address,
socklen_t *restrict address_len);
int shutdown(int socket, int how);
int bind(int socket, const sockaddr_in_t *address, socklen_t address_len);
int listen(int socket, int backlog);
int setsockopt(int socket, int level, int option_name, const void *option_value,
socklen_t option_len);
int fork();
void exit(int status);
所以我猜魔术发生在
start.S
,其中包含
_start
以及一种通过创建全局标签来编码系统调用的特殊方式,这些标签通过并在 r9 中累积值以节省字节:
.intel_syntax noprefix
/* functions: rdi, rsi, rdx, rcx, r8, r9 */
/* syscalls: rdi, rsi, rdx, r10, r8, r9 */
/* ^^^ */
/* stack grows from a high address to a low address */
#define c(x, n) \
.global x; \
x:; \
add r9,n
c(exit, 3) /* 60 */
c(fork, 3) /* 57 */
c(setsockopt, 4) /* 54 */
c(listen, 1) /* 50 */
c(bind, 1) /* 49 */
c(shutdown, 5) /* 48 */
c(accept, 2) /* 43 */
c(socket, 38) /* 41 */
c(close, 1) /* 03 */
c(open, 1) /* 02 */
c(write, 1) /* 01 */
.global read /* 00 */
read:
mov r10,rcx
mov rax,r9
xor r9,r9
syscall
ret
.global _start
_start:
xor rbp,rbp
xor r9,r9
pop rdi /* argc */
mov rsi,rsp /* argv */
call main
call exit
这种理解是否正确? GCC 使用
start.S
中定义的符号对于系统调用,程序从
_start
开始并调用
main
从 C 文件?
httpd.asm
自定义二进制工作?只是结合 C 源代码和开始汇编的手工优化汇编?
最佳答案
(我克隆了 repo 并调整了 .c 和 .S 以使用 clang -Oz: 992 字节更好地编译,从使用 gcc 的原始 1208 字节减少。在我的 fork 中查看 WIP-clang-tuning branch,直到我开始清理它并发送一个pull request可以节省大量成本,例如在循环中使用 .asm
。)
看起来他们需要 lodsb
在调用这些标签中的任何一个之前成为 r9
,或者使用寄存器全局 var 或者 0
to tell GCC to keep its hands off that register permanently 。否则 GCC 会在 gcc -ffixed-r9
中留下任何垃圾,就像其他寄存器一样。
他们的函数是用普通原型(prototype)声明的,而不是 6 个带有虚拟 r9
args 的 args 来让每个调用站点实际上为零 0
,所以这不是他们这样做的方式。
special way of encoding syscalls
r9
。
call write
指令与正确寄存器中的 args 内联可能更紧凑,而不是让它看起来像一个破坏所有调用破坏寄存器的普通函数。特别是如果使用 clang
syscall
编译,它将使用 3 字节
-Oz
/
push 2
而不是 5 字节
pop rax
来设置索书号。
mov eax, 2
/
push imm8
/
pop
与
syscall
的大小相同。)
call rel32
/
.global foo
在手写 asm 中定义函数。
您可以将其视为一个大型函数,其中包含针对不同系统调用的多个入口点。 在 asm 中,执行总是传递到下一条指令,无论标签如何,除非您使用 jump/call/ret 指令。 CPU 不知道标签。
foo:
标签之间没有
switch(){}
的 C
break;
语句,或者像你可以用
case:
跳转到的 C 标签。当然,除了在 asm 中,您可以在全局范围内执行此操作,而在 C 中,您只能转到函数内。在 asm 中,您可以使用
goto
而不是
call
(
goto
)。
static long callnum = 0; // r9 = 0 before a call to any of these
...
socket:
callnum += 38;
close:
callnum++; // can use inc instead of add 1
open: // missed optimization in their asm
callnum++;
write:
callnum++;
read:
tmp=callnum;
callnum=0;
retval = syscall(tmp, args);
或者如果你把它改写成一个尾调用链,我们甚至可以省略
jmp
而是直接失败:如果你有一个足够聪明的编译器,像这样的 C 真的可以编译成手写的 asm。 (你可以解决 arg-type
register long callnum asm("r9"); // GCC extension
long open(args...) {
callnum++;
return write(args...);
}
long write(args...) {
callnum++;
return read(args...); // tailcall
}
long read(args...){
tmp=callnum;
callnum=0; // reset callnum for next call
return syscall(tmp, args...);
}
jmp foo
是参数传递寄存器(RDI、RSI、RDX、RCX、R8),它们只是保持不变。 R9 是 x86-64 System V 的最后一个 arg-passing 寄存器,但他们没有使用任何需要 6 个 args 的系统调用。
args...
需要 5 个参数,所以他们不能跳过
setsockopt
。但是他们能够将 r9 用于其他用途,而不是需要它来传递第 6 个参数。
mov r10, rcx
instead of xor rbp,rbp
。除非他们使用
xor ebp,ebp
构建,否则 GAS 不会为你优化掉 REX 前缀。 (
Does GCC optimize assembly source file? )
gcc -Wa,-Os start.S
(2 个字节,包括 REX)而不是
xchg rax, r9
(REX + opcode + modrm)保存另一个字节。 (
Code golf.SE tips for x86 machine code )
mov rax, r9
,因为我知道 Linux 系统调用号适合 32 位,尽管它不会节省代码大小,因为仍然需要 REX 前缀来编码
xchg eax, r9d
寄存器号。此外,在他们只需要加 1 的情况下,
r9d
只有 3 个字节,而
inc r9d
是 4 个字节(REX + opcode + modrm + imm8)。 (
add r9d, 1
的 no-modrm 短格式编码仅在 32 位模式下可用;在 64 位模式下,它被重新用作 REX 前缀。)
inc
还可以将一个字节保存为
mov rsi,rsp
/
push rsp
(每个 1 个字节),而不是 3 字节的 REX + mov。这将为在
pop rsi
之前使用
xchg edi, eax
返回 main 的返回值腾出空间。
call exit
,或者将系统调用放在
exit
之下,这样他们就可以落入其中,因为
_start
恰好是编号最高的系统调用!或者至少是
exit
因为它们不需要堆栈对齐,并且
jmp exit
比
jmp rel8
更紧凑。
Also how does the separate httpd.asm custom binary work? Just hand-optimized assembly combining the C source and start assembly?
call rel32
label ),并且可能是手动调整的编译器输出。
可能来自对链接的可执行文件 的手动调整反汇编,因此即使对于来自手写 asm 的部分也没有很好的标签名称。 (具体来说,来自
Agner Fog's ?_017:
,它在其 NASM 语法反汇编中使用该格式作为标签。)
objconv
之后指出了诸如
jnz
之类的东西,而不是
cmp
,后者对人类具有更合适的语义意义,因此它的另一个标志是编译器输出,而不是手写。)
jne
的。看来只是运气。自述文件表明只编译 .c 和 .S 对他们有用,他们的 GCC 版本。
r9
组合在一起,输出是一个完整的 ELF 二进制文件,可以运行了。 不是需要链接 + 剥离的 .o,因此您可以考虑文件中的每个字节。
关于c - 这个没有 libc 的 C 程序如何工作?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66851039/
我是 C 语言新手,我编写了这个 C 程序,让用户输入一年中的某一天,作为返回,程序将输出月份以及该月的哪一天。该程序运行良好,但我现在想简化该程序。我知道我需要一个循环,但我不知道如何去做。这是程序
我一直在努力找出我的代码有什么问题。这个想法是创建一个小的画图程序,并有红色、绿色、蓝色和清除按钮。我有我能想到的一切让它工作,但无法弄清楚代码有什么问题。程序打开,然后立即关闭。 import ja
我想安装screen,但是接下来我应该做什么? $ brew search screen imgur-screenshot screen
我有一个在服务器端工作的 UDP 套接字应用程序。为了测试服务器端,我编写了一个简单的 python 客户端程序,它发送消息“hello world how are you”。服务器随后应接收消息,将
我有一个 shell 脚本,它运行一个 Python 程序来预处理一些数据,然后运行一个 R 程序来执行一些长时间运行的任务。我正在学习使用 Docker 并且我一直在运行 FROM r-base:l
在 Linux 中。我有一个 c 程序,它读取一个 2048 字节的文本文件作为输入。我想从 Python 脚本启动 c 程序。我希望 Python 脚本将文本字符串作为参数传递给 c 程序,而不是将
对于一个类,我被要求编写一个 VHDL 程序,该程序接受两个整数输入 A 和 B,并用 A+B 替换 A,用 A-B 替换 B。我编写了以下程序和测试平台。它完成了实现和行为语法检查,但它不会模拟。尽
module Algorithm where import System.Random import Data.Maybe import Data.List type Atom = String ty
我想找到两个以上数字的最小公倍数 求给定N个数的最小公倍数的C++程序 最佳答案 int lcm(int a, int b) { return (a/gcd(a,b))*b; } 对于gcd,请查看
这个程序有错误。谁能解决这个问题? Error is :TempRecord already defines a member called 'this' with the same paramete
当我运行下面的程序时,我在 str1 和 str2 中得到了垃圾值。所以 #include #include #include using namespace std; int main() {
这是我的作业: 一对刚出生的兔子(一公一母)被放在田里。兔子在一个月大时可以交配,因此在第二个月的月底,每对兔子都会生出两对新兔子,然后死去。 注:在第0个月,有0对兔子。第 1 个月,有 1 对兔子
我编写了一个程序,通过对字母使用 switch 命令将十进制字符串转换为十六进制,但是如果我使用 char,该程序无法正常工作!没有 switch 我无法处理 9 以上的数字。我希望你能理解我,因为我
我是 C++ 新手(虽然我有一些 C 语言经验)和 MySQL,我正在尝试制作一个从 MySQL 读取数据库的程序,我一直在关注这个 tutorial但当我尝试“构建”解决方案时出现错误。 (我正在使
仍然是一个初学者,只是尝试使用 swift 中的一些基本函数。 有人能告诉我这段代码有什么问题吗? import UIKit var guessInt: Int var randomNum = arc
我正在用 C++11 编写一个函数,它采用 constant1 + constant2 形式的表达式并将它们折叠起来。 constant1 和 constant2 存储在 std::string 中,
我用 C++ 编写了这段代码,使用运算符重载对 2 个矩阵进行加法和乘法运算。当我执行代码时,它会在第 57 行和第 59 行产生错误,非法结构操作(两行都出现相同的错误)。请解释我的错误。提前致谢:
我是 C++ 的初学者,我想编写一个简单的程序来交换字符串中的两个字符。 例如;我们输入这个字符串:“EXAMPLE”,我们给它交换这两个字符:“E”和“A”,输出应该类似于“AXEMPLA”。 我在
我需要以下代码的帮助: 声明 3 个 double 类型变量,每个代表三角形的三个边中的一个。 提示用户为第一面输入一个值,然后 将用户的输入设置为您创建的代表三角形第一条边的变量。 将最后 2 个步
我是新来的,如果问题不好请见谅 任务:将给定矩阵旋转180度 输入: 1 4 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 输出: 16 15 14 13 12 11
我是一名优秀的程序员,十分优秀!