ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

C-不使用stdlibs打印args

2019-11-08 19:50:34  阅读:238  来源: 互联网

标签:assembly gcc inline-assembly c-3 linux


我刚刚编写了一个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

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有