好吧,你没有具体说,但从你的帖子来看,你似乎正在使用 gcc 及其带有约束语法的内联汇编(其他 C 编译器具有非常不同的内联语法)。也就是说,您可能需要使用 AT&T 汇编器语法而不是 Intel,因为这就是 gcc 所使用的语法。
综上所述,让我们看看您的 write2 函数。首先,您不想创建堆栈帧,因为 gcc 将创建一个堆栈帧,因此如果您在 asm 代码中创建一个堆栈帧,最终将得到两个帧,事情可能会变得非常混乱。其次,由于 gcc 正在布置堆栈帧,因此您无法使用“[ebp + offset]”访问变量,因为您不知道它是如何布置的。
这就是约束的目的——你说你希望 gcc 将值放在什么地方(任何寄存器、内存、特定寄存器)并在 asm 代码中使用“%X”。最后,如果您在 asm 代码中使用显式寄存器,则需要在第三部分(在输入约束之后)列出它们,以便 gcc 知道您正在使用它们。否则,它可能会在其中一个寄存器中放入一些重要的值,而您会破坏该值。
您还需要告诉编译器内联汇编将或可能从输入操作数指向的内存中读取或写入;那是not默示。
综上所述,您的 write2 函数如下所示:
void write2(char *str, int len) {
__asm__ volatile (
"movl $4, %%eax;" // SYS_write
"movl $1, %%ebx;" // file descriptor = stdout_fd
"movl %0, %%ecx;"
"movl %1, %%edx;"
"int $0x80"
:: "g" (str), "g" (len) // input values we MOV from
: "eax", "ebx", "ecx", "edx", // registers we destroy
"memory" // memory has to be in sync so we can read it
);
}
注意 AT&T 语法 — src, dest 而不是 dest, src and%
寄存器名称之前。
现在这可以工作了,但是效率很低,因为它会包含很多额外的 movs。一般来说,您不应该在 asm 代码中使用 mov 指令或显式寄存器,因为您最好使用约束来说明您想要的东西并让编译器确保它们在那里。这样,优化器可能可以摆脱大部分 mov,特别是如果它内联函数(如果您指定 -O3,它将执行此操作)。方便的是,i386 机器模型对特定寄存器有限制,因此您可以这样做:
void write2(char *str, int len) {
__asm__ volatile (
"movl $4, %%eax;"
"movl $1, %%ebx;"
"int $0x80"
:: "c" (str), /* c constraint tells the compiler to put str in ecx */
"d" (len) /* d constraint tells the compiler to put len in edx */
: "eax", "ebx", "memory");
}
甚至更好
// UNSAFE: destroys EAX (with return value) without telling the compiler
void write2(char *str, int len) {
__asm__ volatile ("int $0x80"
:: "a" (4), "b" (1), "c" (str), "d" (len)
: "memory");
}
还要注意使用volatile
这是需要告诉编译器即使它的输出(没有)没有被使用,它也不能被消除。 (asm
没有输出操作数已经是隐式的volatile
,但是当真正的目的不是计算某些东西时,将其明确化并没有什么坏处;这是为了像系统调用这样的副作用。)
edit
最后一点要注意的是——这个函数正在执行一个 write 系统调用,它确实在 eax 中返回一个值——要么是写入的字节数,要么是错误代码。所以你可以通过输出约束得到它:
int write2(const char *str, int len) {
__asm__ volatile ("int $0x80"
: "=a" (len)
: "a" (4), "b" (1), "c" (str), "d" (len),
"m"( *(const char (*)[])str ) // "dummy" input instead of memory clobber
);
return len;
}
所有系统调用都在 EAX 中返回。值来自-4095
to -1
(含)为负数errno
代码,其他值均无错误。 (这适用于全局的所有 Linux 系统调用)。
如果您正在编写通用系统调用包装器,您可能需要一个"memory"
破坏,因为不同的系统调用有不同的指针操作数,并且可能是输入或输出。看https://godbolt.org/z/GOXBue https://godbolt.org/z/GOXBue举个例子,如果你把它省略了,它就会崩溃,并且这个答案 https://stackoverflow.com/questions/34244185/looping-over-arrays-with-inline-assembly有关虚拟内存输入/输出的更多详细信息。
对于此输出操作数,您需要显式volatile
-- 正好一个write
每次系统调用asm
语句在源中“运行”。否则,编译器可以假设它的存在只是为了计算其返回值,并且可以消除使用相同输入的重复调用,而不是编写多行。 (或者如果您没有检查返回值,则将其完全删除。)