gpt4 book ai didi

C - 打印没有标准库的参数

转载 作者:太空狗 更新时间:2023-10-29 11:06:54 25 4
gpt4 key购买 nike

我刚刚编写了一个 C 程序,它在不使用标准库或 main() 函数的情况下打印其命令行参数。我的动机只是好奇心和了解如何玩内联汇编。我将 Ubuntu 17.10 x86_64 与 4.13.0-39 通用内核和 GCC 7.2.0 一起使用。

下面是我的代码,我已经尽可能多地评论了我的理解。系统需要函数 printprint_1my_exit_start() 来运行可执行文件。实际上,如果没有 _start(),链接器将发出警告并且程序将出现段错误。

函数 printprint_1 是不同的。第一个向控制台打印出一个字符串,在内部测量字符串的长度。第二个函数需要将字符串长度作为参数传递。 my_exit() 函数只是退出程序,返回所需的值,在我的例子中是字符串长度或命令行参数的数量。
print_1 需要字符串长度作为参数,因此使用 while() 循环对字符进行计数,并将长度存储在 strLength 中。在这种情况下,一切正常。

当我使用 print 函数时会发生奇怪的事情,该函数在内部测量字符串长度。简单地说,这个函数似乎以某种方式将字符串指针更改为指向环境变量,该变量应该是下一个指针,而不是第一个参数,该函数打印 "CLUTTER_IM_MODULE=xim" ,这是我的第一个环境变量。我的解决方法是在下一行中将 *a 分配给 *b

我在计数过程中找不到任何解释,但看起来它正在改变我的字符串指针。

unsigned long long print(char * str){
unsigned long long ret;
__asm__(
"pushq %%rbx \n\t"
"pushq %%rcx \n\t" //RBX and RCX to the stack for further restoration
"movq %1, %%rdi \n\t" //pointer to string (char * str) into RDI for SCASB instruction
"movq %%rdi, %%rbx \n\t" //saving RDI in RBX for final substraction
"xor %%al, %%al \n\t" //zeroing AL for SCASB comparing
"movq $0xffffffff, %%rcx \n\t" //max string length for REPNE instruction
"repne scasb \n\t" //counting "loop" see details: https://www.felixcloutier.com/x86/index.html for REPNE and SCASB instructions
"sub %%rbx, %%rdi \n\t" //final substraction
"movq %%rdi, %%rdx \n\t" //string length for write syscall
"movq %%rdi, %0 \n\t" //string length into ret to return from print
"popq %%rcx \n\t"
"popq %%rbx \n\t" //RBX and RCX restoration

"movq $1, %%rax \n\t" //write - 1 for syscall
"movq $1, %%rdi \n\t" //destination pointer for string operations $1 - stdout
"movq %1, %%rsi \n\t" //source string pointer
"syscall \n\t"
: "=g"(ret)
: "g"(str)
);
return ret; }

void print_1(char * str, int l){
int ret = 0;

__asm__("movq $1, %%rax \n\t" //write - 1 for syscall
"movq $1, %%rdi \n\t" //destination pointer for string operations
"movq %1, %%rsi \n\t" //source pointer for string operations
"movl %2, %%edx \n\t" //string length
"syscall"
: "=g"(ret)
: "g"(str), "g" (l));}


void my_exit(unsigned long long ex){
int ret = 0;
__asm__("movq $60, %%rax\n\t" //syscall 60 - exit
"movq %1, %%rdi\n\t" //return value
"syscall\n\t"
"ret"
: "=g"(ret)
: "g"(ex)
);}

void _start(){

register int ac __asm__("%rsi"); // in absence of main() argc seems to be placed in rsi register
//int acp = ac;
unsigned long long strLength;
if(ac > 1){
register unsigned long long * arg __asm__("%rsp"); //argv array
char * a = (void*)*(arg + 7); //pointer to argv[1]
char * b = a; //work around for print function
/*version with print_1 and while() loop for counting
unsigned long long strLength = 0;
while(*(a + strLength)) strLength++;
print_1(a, strLength);
print_1("\n", 1);
*/
strLength = print(b);
print("\n");
}
//my_exit(acp); //echo $? prints argc
my_exit(strLength); //echo $? prints string length}

最佳答案

char * a = (void*)*(arg + 7); 完全是一个“碰巧工作”的东西,如果它真的有效的话。除非您正在编写仅使用内联 asm 的 __attribute__((naked)) 函数,否则完全取决于编译器如何布置堆栈内存。看起来您正在获取 rsp ,尽管对于这种不受支持的 register-asm 本地使用并不能保证。 (仅当用作内联 asm 语句的操作数时才能保证使用请求的寄存器。)

如果您在禁用优化的情况下编译,gcc 将为本地人保留堆栈槽,因此 char * b = a; 使 gcc 在函数入口 上通过更多调整 RSP,这就是为什么您的 hack 碰巧更改了 gcc 的代码生成以匹配硬编码的 +7(8 次)字节)偏移量放在源中。

在进入 _start 时,堆栈内容是: argc 处的 (%rsp)argv[] 开始于 8(%rsp) 。在 argv[] 的终止 NULL 指针上方,envp[] 数组也在堆栈内存中。所以这就是为什么当你的硬编码偏移量获得错误的堆栈槽时你会得到 CLUTTER_IM_MODULE=xim 的原因。

// in absence of main() argc seems to be placed in rsi register



这可能是动态链接器(在 _start 之前在您的进程中运行)遗留下来的。如果您使用 gcc -static -nostdlib -fno-pie 编译,您的 _start 将是直接从内核到达的真正进程入口点,所有寄存器 = 0(RSP 除外)。请注意,ABI 表示未定义; Linux 选择将它们归零以避免信息泄漏。

您可以在 GNU C 中编写 void _start(){},无论是否启用优化都可以可靠地工作,并且出于正确的原因工作,没有内联 asm (但仍然依赖于 x86-64 SysV ABI 的调用约定和进程入口堆栈布局) .不需要对 gcc 的代码生成中碰巧发生的偏移进行硬编码。 How Get arguments value using inline assembly in C without Glibc? 。它使用 int argc = (int)__builtin_return_address(0); 之类的东西,因为 _start 不是函数:堆栈上的第一件事是 argc 而不是返回地址。它不漂亮也不推荐,但考虑到调用约定,这就是如何让 gcc 生成知道事物在哪里的代码。

您的代码破坏注册而不告诉编译器。 关于这段代码的一切都是令人讨厌的,没有理由期望它能够始终如一地工作。如果是这样,那是偶然的,并且可能会因不同的周围代码或编译器选项而中断。如果您想编写整个函数,请在独立的 asm(或在全局范围内的内联 asm)中完成并声明一个 C 原型(prototype),以便编译器可以调用它。

查看 gcc 的 asm 输出以了解它围绕您的代码生成了什么。 (例如,将您的代码放在 http://godbolt.org/ 上)。您可能会使用在 asm 中破坏的寄存器看到它。 (除非您在禁用优化的情况下编译,在这种情况下,它不会在 C 语句之间的寄存器中保留任何内容以支持一致调试。只有破坏 RSP 或 RBP 会导致问题;其他内联汇编破坏错误将无法检测到。)但是破坏红色zone 仍然是一个问题。

另请参阅 https://stackoverflow.com/tags/inline-assembly/info 以获取指南和教程的链接。

使用内联 asm 的正确方法(如果有正确的方法)通常是让编译器尽可能多地做 。因此,要进行写入系统调用,您将使用输入/输出约束来执行所有操作,并且 asm 模板中唯一的指令是 "syscall" ,就像这个很好的示例 my_write 函数: How to invoke a system call via sysenter in inline assembly?(实际答案有 32 位 int $0x80 和 x86 -64 syscall ,但不是使用 32 位 sysenter 的内联 asm 版本,因为这不是保证稳定的 ABI)。

另请参阅 What is the difference between 'asm', '__asm' and '__asm__'? 以获取另一个示例。

https://gcc.gnu.org/wiki/DontUseInlineAsm 有很多你不应该使用它的原因(比如打败常量传播和其他优化)。

请注意,内联 asm 语句的指针输入约束并不意味着指向的内存也是输入或输出。使用 "memory" clobber,或查看 at&t asm inline c++ problem 以了解虚拟操作数解决方法。

关于C - 打印没有标准库的参数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/51127399/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com