其他分享
首页 > 其他分享> > C-不使用stdlibs打印args

C-不使用stdlibs打印args

作者:互联网

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

以下是我尝试尽我所能理解的代码.系统需要使用函数print,print_1,my_exit和_start()来运行可执行文件.实际上,如果没有_start(),则链接器将发出警告,并且程序将出现段错误.

函数print和print_1不同.第一个将字符串输出到控制台,在内部测量字符串的长度.第二个函数需要作为参数传递的字符串长度. my_exit()函数只是退出程序,返回所需的值,在我的情况下,该值是字符串长度或命令行参数的数量.

print_1需要将字符串长度作为参数,以便使用while()循环对字符进行计数,并将长度存储在strLength中.在这种情况下,一切正常.

当我使用打印功能时会发生奇怪的事情,该功能在内部测量字符串的长度.简单地说,该函数看起来以某种方式将字符串指针更改为指向环境变量,而该环境变量应该是下一个指针,而不是第一个参数,函数将打印“ 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 local用法. (仅当用作内联asm语句的操作数时,才保证使用请求的寄存器.)

如果在禁用优化的情况下进行编译,则gcc将为本地人保留堆栈插槽,因此char * b = a;使gcc通过更多的函数输入来调整RSP,因此这就是为什么您的黑客碰巧更改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中编写一个无效的_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原型,以便编译器可以调用它.

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

有关指南和教程的链接,另请参见https://stackoverflow.com/tags/inline-assembly/info.

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

另请参见What is the difference between ‘asm’, ‘__asm’ and ‘__asm__’?.

由于许多原因(例如击败常数传播和其他优化),您不应该使用https://gcc.gnu.org/wiki/DontUseInlineAsm.

注意,内联asm语句的指针输入约束并不意味着指向的内存也是输入或输出.请使用“内存”清除器,或参见at&t asm inline c++ problem以了解虚拟操作数的解决方法.

标签:assembly,gcc,inline-assembly,c-3,linux
来源: https://codeday.me/bug/20191108/2010162.html