Chinaunix首页 | 论坛 | 博客
  • 博客访问: 3664138
  • 博文数量: 146
  • 博客积分: 3918
  • 博客等级: 少校
  • 技术积分: 8581
  • 用 户 组: 普通用户
  • 注册时间: 2010-10-17 13:52
个人简介

个人微薄: weibo.com/manuscola

文章分类

全部博文(146)

文章存档

2016年(3)

2015年(2)

2014年(5)

2013年(42)

2012年(31)

2011年(58)

2010年(5)

分类: C/C++

2011-09-11 09:44:10

     读Richard Blum大师的Professional Assembly Language,读到了使用函数一章的查看命令行参数和查看环境变量一节,觉得豁然开朗,手痒就在自己的linux系统上照抄了获取环境变量的那个子程序。一执行发生了段错误。当时觉的很奇怪,就探究了一番。

    Richard Blum大师出错的可能性我觉得比较小(我这个人对老爷子有点盲目崇拜),我觉得有可能是编译器 链接器 加载器结构是不是发生过变化,毕竟他老爷子的书是06年出的。有没有和我遇到相同问题的同学,如果你的linux下,也无法执行Richard Blum 老爷子的查看环境变量的那段代码,通知我我会很高兴的。我下面给出我的Linux版本和gcc的版本。希望牛人指教为什么老爷子的汇编代码不能在我的电脑上执行。
    
  1. root@libin:~/program/assembly/getenv# gcc -v
  2. Using built-in specs.
  3. Target: i486-linux-gnu
  4. Configured with: ../src/configure -v --with-pkgversion='Ubuntu 4.4.3-4ubuntu5' --with-bugurl=file:///usr/share/doc/gcc-4.4/README.Bugs --enable-languages=c,c++,fortran,objc,obj-c++ --prefix=/usr --enable-shared --enable-multiarch --enable-linker-build-id --with-system-zlib --libexecdir=/usr/lib --without-included-gettext --enable-threads=posix --with-gxx-include-dir=/usr/include/c++/4.4 --program-suffix=-4.4 --enable-nls --enable-clocale=gnu --enable-libstdcxx-debug --enable-plugin --enable-objc-gc --enable-targets=all --disable-werror --with-arch-32=i486 --with-tune=generic --enable-checking=release --build=i486-linux-gnu --host=i486-linux-gnu --target=i486-linux-gnu
  5. Thread model: posix
  6. gcc version 4.4.3 (Ubuntu 4.4.3-4ubuntu5)
  7. root@libin:~/program/assembly/getenv# uname -ar
  8. Linux libin 2.6.32-26-generic #48-Ubuntu SMP Wed Nov 24 09:00:03 UTC 2010 i686 GNU/Linux

     当时Richard Blum大师给出了这么一个图,当时我感觉十分技痒,才决定照抄代码,验证一把。事实证明,这张图是有问题的。这是一个错误的图片。
   

        
     首先我对C语言要比汇编熟悉,所以先用C语言查看了下argc argv 环境变量的地址,代码如下
  1. #include<stdio.h>


  2. int main(int argc,char* argv[],char** envp)
  3. {
  4.     printf("the address of para num:is %lx\n",&argc);
  5.     printf("the address of argv is %lx\n",&argv);
  6.     printf("the address of environ is %lx\n",&envp);
  7.     printf("the value of argv is %lx\n",argv);
  8.     printf("the value of environ is %lx\n",envp);
  9.     
  10.     return 0;
  11. }
  
  我用debug跟踪了下:
   
  1. (gdb) b 6
  2. Breakpoint 1 at 0x80483ed: file test.c, line 6.
  3. (gdb) r 1 8
  4. Starting program: /home/libin/program/assembly/getenv/test 1 8
  5. Breakpoint 1, main (argc=3, argv=0xbffff7d4, envp=0xbffff7e4) at test.c:6
  6. 6 printf("the address of para num:is %lx\n",&argc);
  7. (gdb) p $ebp
  8. $1 = (void *) 0xbffff728
  9. (gdb) p &argc
  10. $2 = (int *) 0xbffff730
  11. (gdb) p &argv
  12. $3 = (char ***) 0xbffff734
  13. (gdb) p &envp
  14. $4 = (char ***) 0xbffff738
     我们可以看到,argc所在的地址和argv的地址 环境变量全局指针对应的地址相邻的。
    
  1.     &argc = 0xbffff730
  2.     &argv = 0xbffff734
  3.     &envp = 0xbffff738

       接下来我们对argv 和envp的值比较感兴趣,我们可以查看下这片数据区存的是什么:

   
  1. (gdb) x/20x 0xbffff730
  2. 0xbffff730:    0x00000003    0xbffff7d4    0xbffff7e4    0xb7fff858
  3. 0xbffff740:    0xbffff790    0xffffffff    0x0012bff4    0x08048254
  4. 0xbffff750:    0x00000001    0xbffff790    0x0011d626    0x0012cab0
  5. 0xbffff760:    0xb7fffb48    0x00283ff4    0x00000000    0x00000000
  6. 0xbffff770:    0xbffff7a8    0xb84014ed    0x6f394392    0x00000000

    我们看到,0xbffff730开始的这块数据区,第一个值ox00000003就是argc的值,考虑到我的程序是 r 1 8,的的确确是3个入参。这片数据区告诉我们以下信息
  1. argc = 0x00000003
  2. argv = 0xbffff7d4
  3. envp = 0xbffff7e4
    接下来我们兵分两路,分别查看argv 和envp指向的数据区。
    第一路人马,观察 argv指向的数据区:

  1. (gdb) x/20x 0xbffff7d4
  2. 0xbffff7d4:    0xbffff90a    0xbffff933    0xbffff935    0x00000000
  3. 0xbffff7e4:    0xbffff937    0xbffff942    0xbffff952    0xbffff95c
  4. 0xbffff7f4:    0xbffffdfd    0xbffffe0d    0xbffffe1b    0xbffffe29
  5. 0xbffff804:    0xbffffe35    0xbffffe86    0xbffffe95    0xbffffebd
  6. 0xbffff814:    0xbffffece    0xbffffed7    0xbffffee8    0xbffffeff
    我们知道,argv是指针的指针,它指向的内容是指针,指针的个数由argc决定。OK,argc = 3,所以蓝颜色的三个值,是3个指针,这三个指针指向的数据区才是真真正正的数据,即 入参字符串。
    我们分别看下这三个指针指向的字符串分别是啥.

  1. (gdb) x/s 0xbffff90a
  2. 0xbffff90a: "/home/libin/program/assembly/getenv/test"
  3. (gdb) x/s 0xbffff933
  4. 0xbffff933: "1"
  5. (gdb) x/s 0xbffff935
  6. 0xbffff935: "8"
       啥也不说了,眼泪汪汪的,终于见到我们要找的入参了。第一个参数是我的可执行程序,第二个参数是1,第三个参数是8。

     兵分两路,第一路人马已经胜利,下面看第二路人马,查看环境变量。
  •     envp = 0xbffff7e4
    1. (gdb) x/40x 0xbffff7e4
    2. 0xbffff7e4: 0xbffff937 0xbffff942 0xbffff952 0xbffff95c
    3. 0xbffff7f4: 0xbffffdfd 0xbffffe0d 0xbffffe1b 0xbffffe29
    4. 0xbffff804: 0xbffffe35 0xbffffe86 0xbffffe95 0xbffffebd
    5. 0xbffff814: 0xbffffece 0xbffffed7 0xbffffee8 0xbffffeff
    6. 0xbffff824: 0xbfffff07 0xbfffff19 0xbfffff26 0xbfffff46
    7. 0xbffff834: 0xbfffff53 0xbfffff61 0xbfffff83 0xbfffffba
    8. 0xbffff844: 0x00000000 0x00000020 0x0012d420 0x00000021
    9. 0xbffff854: 0x0012d000 0x00000010 0xbfebf3ff 0x00000006
    10. 0xbffff864: 0x00001000 0x00000011 0x00000064 0x00000003
    11. 0xbffff874: 0x08048034 0x00000004 0x00000020 0x00000005
  •     环境变量envp也是指针的指针,所以这篇数据区蓝色的部分,都是指针,这些蓝色的指针,指向的就是我们的环境变量字符串。
    1. (gdb) x/s 0xbffff937
    2. 0xbffff937: "TERM=xterm"
    3. (gdb) x/s 0xbffff942
    4. 0xbffff942: "SHELL=/bin/bash"
    5. (gdb) x/s 0xbffff952
    6. 0xbffff952: "USER=root"
         啥也不说了,我就不一一列举每个环境变量了,毕竟咱不是古龙那厮,靠行数来赚稿费。需要说明的地方是argv指向的三个蓝色指针之后面,有0x00000000,这个之后就是环境变量那一排蓝色指针了。参见上面那张图。

        如果你跟着我一步步走下来,你也就明白Richard Blum 绘制的那张图有什么问题了。本来想绘制个图,今天有其他的学习任务,所以就不画图了,各位看官见谅。

        下面给出通过修改后的,汇编获取环境变量的代码:

    1. # getenv list system environment variables

    2. .section .data
    3. output:
    4.     .asciz "%s\n"
    5. .section .text
    6. .globl main
    7. main:
    8.         movl %esp,%ebp
    9.         addl $12,%ebp
    10.         movl (%ebp),%ebx
    11. loop1:
    12.         cmpl $0,(%ebx)
    13.         je endls
    14.         pushl (%ebx)
    15.         pushl $output
    16.         call printf
    17.         addl $12,%esp
    18.         addl $4,%ebx
    19.         loop loop1
    20. endls:
    21.         pushl $0
    22.         call exit
         编译执行结果如下:

    1. root@libin:~/program/assembly/getenv# gcc -o getenv getenv.s
    2. root@libin:~/program/assembly/getenv# ./getenv
    3. SHELL=/bin/bash
    4. TERM=xterm
    5. USER=root
    6. LS_COLORS=rs=0:di=01;34:ln=01;36:hl=44;37:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arj=01;31:*.taz=01;31:*.lzh=01;31:*.lzma=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.bz2=01;31:*.bz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.rar=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.jpg=01;35:*.jpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.axv=01;35:*.anx=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.axa=00;36:*.oga=00;36:*.spx=00;36:*.xspf=00;36:
    7. SUDO_USER=libin
    8. SUDO_UID=1000
    9. USERNAME=root
    10. PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/X11R6/bin
    11. PWD=/home/libin/program/assembly/getenv
    12. LANG=zh_CN.UTF-8
    13. SHLVL=1
    14. SUDO_COMMAND=/bin/bash
    15. HOME=/home/libin
    16. LANGUAGE=zh_CN:en
    17. LOGNAME=root
    18. LESSOPEN=| /usr/bin/lesspipe %s
    19. SUDO_GID=1000
    20. DISPLAY=:0.0
    21. LESSCLOSE=/usr/bin/lesspipe %s %s
    22. COLORTERM=gnome-terminal
    23. XAUTHORITY=/var/run/gdm/auth-for-libin-ckEkiQ/database
    24. OLDPWD=/home/libin/program/assembly
    25. _=./getenv
     P.S. 本文绝没有对Richard Blum 大师有一丝不敬的意思,恰恰相反,Richard Blum老爷子是我崇拜的偶   像。

    参考文献:
    1 Professional Assembly Language  Richard Blum著

    阅读(10178) | 评论(5) | 转发(3) |
    给主人留下些什么吧!~~

    astrotycoon2015-03-18 11:15:23

    我想说的是Richard Blum是没有错的,他老人家指的是程序运行到_start处时的栈分布情况,而你的程序已经时运行到main了,所以此时的栈分布情况肯定不一样了啊:_start到main者期间编译器已经偷偷地加入了很多代码了。你也可以使用gcc的-nostartfiles选项!

    Bean_lee2012-11-08 12:41:02

    jamcode: 原书作者说的没有错,只是因为你用的是gcc编译的。请见我的这个文章:
    http://jamcode.iteye.com/blog/1720216.....
    日,我的博文出了2.0 了,呵呵,这个消息震撼,好,晚上下班我研究下。
    谢谢兄弟告知。我搞清楚,争取出个3.0 ,呵呵

    jamcode2012-11-08 12:03:34

    原书作者说的没有错,只是因为你用的是gcc编译的。请见我的这个文章:
    http://jamcode.iteye.com/blog/1720216

    GFree_Wind2011-10-11 12:25:04

    还是尽信书不如无书啊。期待博主的正确的图

    Heartwork2011-09-19 12:24:24

    这个应该是Richard Blum错了。

    正常传入的三个参数:argc,argv,envp需要在编译器获得相应的地址,如果堆栈的信息是按照楼主最开始给出的那张图来组织的话,编译器是无法获得envp的地址的。