C-不使用stdlibs打印args

时间:2018-07-01 22:20:01

标签: c linux gcc assembly inline-assembly

我刚刚编写了一个C程序,该程序在不使用标准库或Program.cs函数的情况下打印其命令行参数。我的动机只是出于好奇心,并了解如何进行内联汇编。我正在将Ubuntu 17.10 x86_64和4.13.0-39通用内核以及GCC 7.2.0一起使用。

下面是我的代码,据我所知,我已尝试对其进行注释。系统需要功能main()printprint_1my_exit来运行可执行文件。实际上,在没有_start()的情况下,链接器将发出警告,并且程序将出现段错误。

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

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

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

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

*b

2 个答案:

答案 0 :(得分:2)

char * a = (void*)*(arg + 7);完全是一种“可以工作的东西”,如果它可以工作的话。除非您编写的__attribute__((naked))函数仅 使用内联asm,否则完全由编译器决定如何布置堆栈内存。看来您正在使用rsp,尽管不能保证这种不支持的register-asm local用法。 (只有在用作内联asm语句的操作数时,才保证使用请求的寄存器。)

如果您在禁用优化的情况下进行编译,则gcc会为本地人保留堆栈插槽,因此char * b = a;使gcc通过函数输入上的更多内容来调整RSP ,因此这就是您的黑客碰巧更改gcc代码的原因-gen以匹配您在源代码中放置的硬编码+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原型,以便编译器可以调用它。

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

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


使用内联汇编的正确方法(如果有正确的方法)通常是让编译器尽可能多地进行操作。因此,要进行写系统调用,您需要在输入/输出约束下进行所有操作,而asm模板中的唯一指令就是"syscall",就像这个不错的示例my_write函数:{{3} }(实际答案有32位int $0x80和x86-64 syscall,但不是使用32位sysenter的嵌入式asm版本,因为这不是保证稳定的ABI)。< / p>

另请参见How to invoke a system call via sysenter in inline assembly?

What is the difference between 'asm', '__asm' and '__asm__'?出于很多原因,您不应该使用它(例如击败常数传播和其他优化方法)。

请注意,内联asm语句的指针输入约束不会暗示指向的内存也是输入或输出。使用"memory"垃圾桶,或使用https://gcc.gnu.org/wiki/DontUseInlineAsm了解虚拟操作数的解决方法。

答案 1 :(得分:1)

非常感谢您在回答和评论中提出的每条建议,这确实很有帮助。 Peter Cordes,谢谢您的链接https://stackoverflow.com/a/50261819。 我使用此代码作为基础,并按照您的建议在全局范围内编写内联汇编。 经过几天的浏览并阅读了一些文档,终于有了执行我一直在寻找的代码(检查没有stdlib的命令行参数和环境变量)。

欢迎任何改进和建议。

使用以下命令编译:gcc -Wall -o getArgs getArgs.c -nostdlib -nostartfiles -fno-ident -static -s

运行:./getArgs -args大家好-envs

*** Environment variable ***
/bin/bash

*** Command line arguments ***
-args
Hello
everybody
-envs

呼叫约定: 用户级应用程序用作整数寄存器以传递序列  *%rdi,%rsi,%rdx,%rcx,%r8和%r9。因此在函数调用中,我们应该有ex。打印(%rdi,%rsi,%rdx);  *内核接口使用%rdi,%rsi,%rdx,%r10,%r8和%r9。

asm(
    ".global _start\n\t"
    "_start:\n\t"
    "   xorl %ebp,%ebp\n\t"                     // Clear the frame pointer. As ABI suggests
    "   movq 0(%rsp),%rdi\n\t"                  // argc
    "   lea 8(%rsp),%rsi\n\t"                   // argv = %rsp + 8
    "   lea   8(%rsp,%rdi,8), %rdx\n\t"         // pointer to environment variables     (8*(argc+1))(%rsp)  envp[0]
    "   call __main\n\t"                        // call main function
    "   movq %rax,%rdi\n\t"                     // main return code as an argument for exit syscall
    "   movl $60,%eax\n\t"                      // 60 = exit
    "   syscall\n\t");
asm(
    "print:\n\t"                    // thanks to the calling convention when we call our print we get:  int fd (%rdi), const void *buf (%rsi), unsigned count (%rdx)
    "   movq $1,%rax\n\t"         // 1 = write syscall on x86_64
    "   syscall\n\t"
    "   ret\n\t"
);



int print(int fd, const void *buf, unsigned count); //do not forget to declare function from inline assembly


unsigned strLen(const char *ch) {
    const char *ptr;
    for(ptr = ch; *ptr; ++ptr);             //ptr points to same place as ch, then looping until *ptr is not 0. If so, after substraction we get string length.
    return ptr-ch;      }                   //"When you substract two pointers, as long as they point into the same array, the result is the number of elements separating them"

char strCmp(const char * a, const char * b){
     char t = 0;
     int aLength = strLen(a);
     int bLength = strLen(b);
    if(aLength == bLength){
        for(int j = 0; j < aLength; j++){
            if(a[j] == b[j])
                t++;
        }
        if(t == aLength)
            return 1;
        else
            return 0;
    }else{
        return 0;
}}   //strCmp - comparing 2 strings up to the length of first string, returns 1 if equal and 0 if not

char * getEnv(char * env, char **envp){
     char * val;
     int valL = strLen(env);
     int k = 1;
//environment variables is null terminated array of strings, last array element is 0
while(*(envp + k)){
    char t = 0;
    for(val = *(envp + k); *val != 0x3d; ++val);        //counting up to 3d (=) //ascii hex of "=" is 0x3d
    int envpL = val - *(envp + k);                      //counting length of envp
    if(valL == envpL){
        for(int j = 0; j < valL; j++){
            if(*(*(envp + k) + j) == *(env + j)){
                t++;
            }
        }
        if(t == valL){
            return ++val;
        }
    }
    k++;
}

return "";}     //getEnv - looping through environment variables "envp" looking for "env", using strLen()


int __main(int argc, char **argv, char **envp) {
         char arg1 = 0, arg2 = 0; int length;                       //arg1, arg2 - flags for argv checking
          //arrays to compare with command line arguments
         char envs[6] = {0x2d, 0x65, 0x6e, 0x76, 0x73, 0x00};           //ascii hex of "-envs"      
         char args[6] = {0x2d, 0x61, 0x72, 0x67, 0x73, 0x00};   //ascii hex of "-args"      
         //first of all we check for control arguments
        for(int i = 1; i < argc; i++) {

              if(strCmp(*(argv + i), envs))
                        arg1 = 1;
              if(strCmp(*(argv + i), args))
                        arg2 = 1;
         }

       if(arg1){
           char * b = getEnv("SHELL", envp);          //we are looking for "SHELL"
           print(1, "*** Environment variable ***\n", 30);
           print(1, b, strLen(b));
           print(1, "\n", 1);
       }
       if(arg2){
           print(1, "\n", 1);
           print(1, "*** Command line arguments ***\n", 31);
           for(int i = 1; i < argc; i++) {
               length = strLen(*(argv + i));
               print(1, *(argv + i), length);
               print(1, "\n", 1);
            }
        }

       return argc; }//number of arguments