ICode9

精准搜索请尝试: 精确搜索
首页 > 系统相关> 文章详细

从两个程序看Linux下命令行参数及execve内核实现

2019-03-06 21:45:05  阅读:272  来源: 互联网

标签:可执行文件 00 tsecer envp argv 内核 Linux bprm execve


一、两个测试程序
[tsecer@Harry ArgLayout]$  cat ArgLayout.c
/*
*简单测试程序,创建命令行参数中指定的进程,但是将execve的第二个参数(也就是子进程的argv数组)修改成随机无意义值
*/
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main(int argc, char * argv[],char * envp[])
{
pid_t forker = fork();

if(0 == forker)
{
char * myargv[] = {
"Hello",
"world",
NULL,
};
execve(argv[1],myargv,envp);
} else if(-1 == forker)
{
fprintf(stderr,"fork failed\n");
} else{
sleep(10000);
}
}
[tsecer@Harry ArgLayout]$ cat mysleeper.c
/*
*测试程序,打印自己的argv,envp数组以及一个根据内核参数布局而计算出来的真实可执行文件名称
*/
#include <stdlib.h>
int dumpxv(char * argv[])
{
int i=0;
if (argv)  while(argv[i]) printf("%s\n",argv[i++]);
return i;
}
int main(int argc,char * argv[], char * envp[])
{
    int vc;
    dumpxv(argv);
    if(vc = dumpxv(envp))
    printf("%s\n",envp[vc-1]+ strlen(envp[vc-1])+1);
    sleep(1000);
}
[tsecer@Harry ArgLayout]$ ./ArgLayout.c.exe ./././././././././././././mysleeper.c.exe 
Hello
world 这两个是子进程看到的argv数组,之后是子进程看到的envp数组。
ORBIT_SOCKETDIR=/tmp/orbit-tsecer
HOSTNAME=Harry
IMSETTINGS_INTEGRATE_DESKTOP=yes
……
_=./ArgLayout.c.exe
./././././././././././././mysleeper.c.exe这里是通过非正统的printf("%s\n",envp[vc-1]+ strlen(envp[vc-1])+1);打印的可执行文件的名称。
在另一个窗口中看这两个程序
tsecer   32299  0.0  0.0   1740   284 pts/3    S+   20:42   0:00 ./ArgLayout.c.e
tsecer   32300  0.0  0.0   1744   316 pts/3    S+   20:42   0:00 Hello world 通过ps看到进程的显示和路径及名称没有任何关系。
这里需要说明的有:
1、通过ps看到的子进程的名字是没有意义的,就是execve中第二个参数给出的一个参数列表,子进程对这个内容没有任何分辨内容,完全照单接受。所以在子进程中通过argv[0]看到的内容完全不是自己真实可执行文件的名称,所以如果想从这个argv中找到可执行文件的名称或者路径,并不是天经地义的,只是说由于通常是通过bash执行的命令,而大家都自觉的遵守了这个约定,所以没出问题。
2、在envp字符串之后,放置着execve的第一个参数,也就是真正的传入的可执行文件的原始信息,这个是靠谱的,因为如果这个是一个鬼扯的地址,那么子进程是无法派生成功的。遗憾的是这个内容对于这种C程序的argc、argv、envp来说是不可见的,也就是这个可靠的内容是不正统的(相对于那个正统的是不可靠的)。
二、如何获得一个指定pid进程使用的可执行文件
这一点大家首先应该想到的是gdb的一个功能,就是gdb启动之后通过attach直接来调试一个制定pid的任务,那么这个gdb必须要通过这个pid找到这个进程使用的可执行文件,我们来围观一下万能的gdb是如何实现的。
gdb-6.5\gdb\linux-nat.c
/* Accepts an integer PID; Returns a string representing a file that
   can be opened to get the symbols for the child process.  */
char *
child_pid_to_exec_file (int pid)
{
  char *name1, *name2;

  name1 = xmalloc (MAXPATHLEN);
  name2 = xmalloc (MAXPATHLEN);
  make_cleanup (xfree, name1);
  make_cleanup (xfree, name2);
  memset (name2, 0, MAXPATHLEN);

  sprintf (name1, "/proc/%d/exe", pid);
  if (readlink (name1, name2, MAXPATHLEN) > 0)
    return name2;
  else
    return name1;
}
实现是简明扼要,就是通过readlink系统调用来扫描这个任务的/proc/pid/exe,找到这个线程对应的可执行文件。这里做个实现,对于刚才那个错误参数的程序,通过ll看一下这个程序的链接,可以看到它指向的位置还是准确的,虽然它的argv是错误的。
[tsecer@Harry KernelDebug]$ ll /proc/32512/exe
lrwxrwxrwx. 1 tsecer tsecer 0 2012-02-29 21:21 /proc/32300/exe -> /home/tsecer/CodeTest/ArgLayout/mysleeper.c.exe
三、proc/pid/exe是如何知道可执行文件正确路径的
linux-2.6.21\fs\proc\task_mmu.c
int proc_exe_link(struct inode *inode, struct dentry **dentry, struct vfsmount **mnt)
vma = mm->mmap;
    while (vma) {
        if ((vma->vm_flags & VM_EXECUTABLE) && vma->vm_file)
            break;
        vma = vma->vm_next;
    }

    if (vma) {
        *mnt = mntget(vma->vm_file->f_path.mnt);
        *dentry = dget(vma->vm_file->f_path.dentry);
        result = 0;
    }
我们cat /proc/pid/maps
[tsecer@Harry KernelDebug]$ cat /proc/32512/maps
001e8000-00206000 r-xp 00000000 fd:00 1280       /lib/ld-2.11.2.so
00206000-00207000 r--p 0001d000 fd:00 1280       /lib/ld-2.11.2.so
00207000-00208000 rw-p 0001e000 fd:00 1280       /lib/ld-2.11.2.so
0020a000-0037c000 r-xp 00000000 fd:00 1282       /lib/libc-2.11.2.so
0037c000-0037d000 ---p 00172000 fd:00 1282       /lib/libc-2.11.2.so
0037d000-0037f000 r--p 00172000 fd:00 1282       /lib/libc-2.11.2.so
0037f000-00380000 rw-p 00174000 fd:00 1282       /lib/libc-2.11.2.so
00380000-00383000 rw-p 00000000 00:00 0 
005a0000-005a1000 r-xp 00000000 00:00 0          [vdso]
08048000-08049000 r-xp 00000000 fd:00 459938     /home/tsecer/CodeTest/ArgLayout/mysleeper.c.exe
08049000-0804a000 rw-p 00000000 fd:00 459938     /home/tsecer/CodeTest/ArgLayout/mysleeper.c.exe
b776c000-b776d000 rw-p 00000000 00:00 0 
b7781000-b7783000 rw-p 00000000 00:00 0 
bf882000-bf897000 rw-p 00000000 00:00 0          [stack]
可以看到,其中的第一个具有可执行属性的区间对应的文件是/lib/ld-2.11.2.so,但是显式的为什么是正确的呢?
…………沉默五秒钟……
其实maps中显示的那个x属性是可执行属性,对应的内核标志位
#define VM_EXEC        0x00000004
而这里判断的是
#define VM_EXECUTABLE    0x00001000
属性,两个是不同的,这个VM_EXECUTABLE属性是在load_elf_binary中单独对加载的可执行文件的时候设置的:
        elf_flags = MAP_PRIVATE | MAP_DENYWRITE | MAP_EXECUTABLE;
现在大家觉得很好笑,但是这个问题我还是困惑了很久了的,所以我就调试了一下才找到这里来的。
四、printf("%s\n",envp[vc-1]+ strlen(envp[vc-1])+1);为什么可以还原原始的exeve第一个参数
linux-2.6.21\fs\exec.c
retval = copy_strings_kernel(1, &bprm->filename, bprm);
    if (retval < 0)
        goto out;

    bprm->exec = bprm->p;
    retval = copy_strings(bprm->envc, envp, bprm);
    if (retval < 0)
        goto out;

    retval = copy_strings(bprm->argc, argv, bprm);
可以看到,在赋值envp数组的内容之前,内核先通过copy_strings_kernel(1, &bprm->filename, bprm)将用户提供的exeve的第一个参数对应的字符串放在了紧邻着envp数组的上面,所以通过envp[vc-1]+ strlen(envp[vc-1])+1就可以知道这个数组的内容。
那么这个内容到底有什么作用,内核在哪里用到了,用户如何引用?这些问题我想了一段时间(大家断断续续想了几十分钟),然后在网上搜索了一段时间,看书看了一段时间(包括《情景分析》和《ULK》),都没有找到确切的说法(很扫兴,恩?),设置说没有找到有说法的地方,当然最好看一下内核的ChangLog,但是我没这方面的经验,所以我就猜测一下这个的意义:这个保存操作是在do_execve函数中完成的,这个函数是一个可执行文件格式无关的函数,elf格式在用、a.out在用,script在用,misc也在用。所以这里把他压在堆栈的最顶端一个猥琐的位置是为了便于扩展,某些特殊的可执行文件格式(例如,一个我不知道的可执行格式)可能会用到这个字符串,虽然我们通常只认识argc,argv,envp等参数。
例如考虑一个文件格式文件,一个
[tsecer@Harry ArgLayout]$ ./demo.sh -c "echo hello" &
[1] 32739
[tsecer@Harry ArgLayout]$ ps aux
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0   2044   704 ?        Ss   03:54   0:02 /sbin/init
……
tsecer   32739  0.0  0.1   4924  1064 pts/3    S    21:52   0:00 /bin/sh ./demo.sh -c echo hell
tsecer   32740  0.0  0.0   3940   476 pts/3    S    21:52   0:00 sleep 1000
tsecer   32741  0.0  0.0   4692   992 pts/3    R+   21:52   0:00 ps aux
[tsecer@Harry ArgLayout]$ cat demo.sh 
#! /bin/sh
sleep 1000
可以看到,命令行中输入的命令被替换,第一个参数./demo.sh会作为新派生的/bin/sh的第一个参数。
linux-2.6.21\fs\binfmt_script.c
    remove_arg_zero(bprm);
    retval = copy_strings_kernel(1, &bprm->interp, bprm);
不过这里用的不是do_execve中拷贝到顶端的字符串,而是所以其内容还是没有被使用到。
五、remove_arg_zero
这个函数主要是清除argv[0]的字符串内容,然后将argc减一。
void remove_arg_zero(struct linux_binprm *bprm)
{
    if (bprm->argc) {
        unsigned long offset;
        char * kaddr;
        struct page *page;

        offset = bprm->p % PAGE_SIZE;
        goto inside;这里是一个无条件跳转。

        while (bprm->p++, *(kaddr+offset++)) {循环结束的条件就是遇到一个零字符*(kaddr+offset++),同时增加bprm->p的值,即递增p指针,这个参数是自底向上增加的,并且argv[0]在最低地址。这里的循环主要是为了解决argv[0]使用的字符串跨越页面的情况。
            if (offset != PAGE_SIZE)
                continue;
            offset = 0;
            kunmap_atomic(kaddr, KM_USER0);
inside:
            page = bprm->page[bprm->p/PAGE_SIZE];
            kaddr = kmap_atomic(page, KM_USER0);
        }
        kunmap_atomic(kaddr, KM_USER0);
        bprm->argc--;
    }
}

六、有啥意义
这一点在busybox所谓的“多路可执行文件”中是非常有用的,因为所有的可执行文件都是软符号链接,所以在执行的时候调用的execve("/bin/cat","/bin/cat"),这样虽然真正执行的是相同的可执行文件,但是它的参数argv却是原始的链接名,所以通过argv来区分功能,在busybox的busybox可执行文件的入口,是通过下面的方法来确定需要执行什么命令
int lbb_main(char **argv)--->>>bb_basename
const char* FAST_FUNC bb_basename(const char *name)
{
    const char *cp = strrchr(name, '/');即最后一个路径分隔符之后的字符作为功能选择依据
    if (cp)
        return cp + 1;
    return name;
}

我在以前编iptable工具的时候,发现它也是一个多路程序:
[tsecer@Harry ArgLayout]$ ll /sbin/iptabl*
lrwxrwxrwx. 1 root root    14 2011-03-12 16:59 /sbin/iptables -> iptables-multi
-rwxr-xr-x. 1 root root 57756 2009-09-17 17:17 /sbin/iptables-multi
lrwxrwxrwx. 1 root root    14 2011-03-12 16:59 /sbin/iptables-restore -> iptables-multi
lrwxrwxrwx. 1 root root    14 2011-03-12 16:59 /sbin/iptables-save -> iptables-multi

标签:可执行文件,00,tsecer,envp,argv,内核,Linux,bprm,execve
来源: https://www.cnblogs.com/tsecer/p/10486196.html

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

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

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

ICode9版权所有