linux下C程式擷取絕對路徑各種方法分析

來源:互聯網
上載者:User
昨天肚腩群裡有人問LINUX下C如何擷取程式絕對路徑。初看這問題,肚腩覺得很簡單啊,就用getcwd或者argv[0]就可以了。寫了個程式試試,
#include <unistd.h>int main(int argc,char *argv[]){    char buffer[100];    getcwd(buffer, sizeof(buffer));    printf("The current directory is: %s\n", buffer);    printf("prog name : %s \n" , argv[0]);    return 0;}
編譯:
gcc -Wall -pipe -g -static -o getpath getpath.c 
發現,getcwd不行,這個函數返回的是當時的執行路徑,如果在其他目錄執行該程式,如~/.這樣調用就不行了。
argv[0]也不行,程式的第一個參數未必是絕對路徑,比如使用/home/../../root/getpath這樣調用,那麼第一個參數就變成了這樣的相對路徑。 肚腩改進了這個程式,用realpath把相對路徑轉換成絕對路徑:
#include  <unistd.h>main(int argc, char * argv[]){    char  resolved_path[80];    realpath(argv[0],resolved_path);    printf("resolved_path: %s\n", resolved_path);    sleep(300);}
OK.這個時候argv[0]用相對路徑調用的問題解決了。BUGS但隨後肚腩又發現存在另外一個問題,比如把程式放到/usr/bin這些存在於環境變數PATH的目錄下,然後在/home目錄下通過程式名直接執行這個程式,輸出的程式絕對路徑就變成了/home/getpath,實際上應該是/usr/bin/getpath。 ELF檔案裡是否有相關資訊還有沒有其它辦法,肚腩先試了下在這個ELF檔案下是否有一些絕對路徑的資訊。
執行:strings getpath| grep getpath
自然是沒有找到了。從另一個方向想想,如果絕對路徑的資訊儲存在ELF檔案中,那麼改變一個程式的路徑,連程式的內容都要修改?所以是不可能滴。 嘗試從進程記憶體擷取那麼這個絕對路徑的資訊,只能是程式啟動並執行時候,加入到進程空間裡面的。我們看看是否能否進程記憶體擷取?
寫一個代碼看下系統的記憶體位址分布情況:
#include <stdio.h>#include <string.h>#include<stdlib.h> int a = 0; //全域初始化區char *p1; //全域未初始化區 main(int argc,char **argv){int b; //棧char s[] = "abc"; //棧char *p2; //棧char *p3 = "123456";// 123456\0在常量區,p3在棧上。static int c =0;// 全域(靜態)初始化區static int uc,uc1,uc2;// 全域(靜態)未初始化區p1 = (char *)malloc(10);p2 = (char *)malloc(20);//分配得來得10和20位元組的地區就在堆區。strcpy(p1, "123456");// 123456\0放在常量區,編譯器可能會將它與p3所指向的"123456"最佳化成一個地方。 printf("堆p1 \t\t\t%p\n",p1);printf("堆p2 \t\t\t%p\n",p2);printf("棧&p3 \t\t\t%p\n",&p3);printf("棧&p2 \t\t\t%p\n",&p2);printf("棧s \t\t\t%p\n",s);printf("棧&s[1] \t\t%p\n",&s[1]);printf("棧&b \t\t\t%p\n",&b);printf("main地址\t\t%p\n",main);printf("文本常量區\t\t%p\n",p3);printf("全域初始化區\t\t%p\n",&a);printf("(靜態)初始化區\t%p\n",&c);printf("全域未初始化區\t\t%p\n",&p1);printf("(靜態)未初始化區\t%p\n",&uc);printf("(靜態)未初始化區\t%p\n",&uc1);printf("(靜態)未初始化區\t%p\n",&uc2); char *p;if(p=getenv("PATH")){        printf("USER=%p\n",p);}printf("ARGC\t%p\n",&argc);printf("ARGV\t%p\n",argv);printf("ARGV\t%p\n",*argv);sleep(300);}
 這裡要指出一下,因為肚腩的系統是x86-64.64為系統,這裡用gcc列印地址的是要注意是使用%p,不用去用%x.用uname -a可以查看系統版本。 
編譯一下:gcc -Wall -pipe -g -static -o memoryshow memoryshow.c 
執行,結果是:
[root@localhost ~]# ./memoryshow 堆p1                    0xa3e8b0堆p2                    0xa3e8d0棧&p3                   0x7fff94fec4a0棧&p2                   0x7fff94fec4a8棧s                     0x7fff94fec4b0棧&s[1]                 0x7fff94fec4b1棧&b                    0x7fff94fec4b4main地址                0×400494文本常量區              0x47f3b0全域初始化區            0x6a6e50(靜態)初始化區        0x6a6e54全域未初始化區          0x6a9ba8(靜態)未初始化區      0x6a6e58(靜態)未初始化區      0x6a6e5c(靜態)未初始化區      0x6a6e60USER=0x7fff94fece29ARGC    0x7fff94fec49cARGV    0x7fff94fec598ARGV    0x7fff94fec82b
 查看下這個進程的map情況:
[root@localhost ~]# ps -ef | grep showmemoryroot      2715  2150  0 21:58 pts/1    00:00:00 grep showmemory[root@localhost ~]# ps -ef | grep memoryroot      2713  2087  0 21:58 pts/0    00:00:00 ./memoryshowroot      2717  2150  0 21:58 pts/1    00:00:00 grep memory[root@localhost ~]# cat /proc/2713/maps 00400000-004a6000 r-xp 00000000 fd:00 524196                             /root/memoryshow006a6000-006a7000 rw-p 000a6000 fd:00 524196                             /root/memoryshow006a7000-006aa000 rw-p 00000000 00:00 0 00a3d000-00a60000 rw-p 00000000 00:00 0                                  [heap]7f050a5c2000-7f050a5c3000 rw-p 00000000 00:00 0 7fff94fd8000-7fff94fed000 rw-p 00000000 00:00 0                          [stack]7fff94fff000-7fff95000000 r-xp 00000000 00:00 0                          [vdso]ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
 再次重複一下操作,肚腩需要看下進程哪些地址會改變,那些不會改變。網上一般的32為線性分布,和肚腩的系統對不上號。所以自己寫個程式一看就清晰明了了。
[root@localhost ~]# ./memoryshow 堆p1                    0x76b8b0堆p2                    0x76b8d0棧&p3                   0x7fff9987a9f0棧&p2                   0x7fff9987a9f8棧s                     0x7fff9987aa00棧&s[1]                 0x7fff9987aa01棧&b                    0x7fff9987aa04main地址                0×400494文本常量區              0x47f3b0全域初始化區            0x6a6e50(靜態)初始化區        0x6a6e54全域未初始化區          0x6a9ba8(靜態)未初始化區      0x6a6e58(靜態)未初始化區      0x6a6e5c(靜態)未初始化區      0x6a6e60USER=0x7fff9987ce29ARGC    0x7fff9987a9ecARGV    0x7fff9987aae8ARGV    0x7fff9987c82b
其對應的MAP情況:
[root@localhost ~]# cat /proc/2713/maps cat: /proc/2713/maps: No such file or directory[root@localhost ~]# ps -ef | grep memoryroot      2719  2087  0 21:59 pts/0    00:00:00 ./memoryshowroot      2722  2150  0 21:59 pts/1    00:00:00 grep memory[root@localhost ~]# cat /proc/2719/maps 00400000-004a6000 r-xp 00000000 fd:00 524196                             /root/memoryshow006a6000-006a7000 rw-p 000a6000 fd:00 524196                             /root/memoryshow006a7000-006aa000 rw-p 00000000 00:00 0 0076a000-0078d000 rw-p 00000000 00:00 0                                  [heap]7f9987494000-7f9987495000 rw-p 00000000 00:00 0 7fff99868000-7fff9987d000 rw-p 00000000 00:00 0                          [stack]7fff999ff000-7fff99a00000 r-xp 00000000 00:00 0                          [vdso]ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
 這裡可以看到,前面三個段的地址是不變的,但堆開始,後面的高位位址區段每次運行都會改變。如果包含檔案絕對路徑的資料是落在前面三個段,那麼可以通過地址直接擷取,如果是在後面,可能就要通過計算擷取。用GDB調試下,看看何種情況:
[root@localhost ~]# gdb memoryshowReading symbols from /root/memoryshow…done.(gdb) b mainBreakpoint 1 at 0x4004a3: file memoryshow.c, line 11.(gdb) rStarting program: /root/memoryshow Breakpoint 1, main (argc=1, argv=0x7fffffffe5c8) at memoryshow.c:1111      char s[] = "abc"; //棧(gdb) b 44Breakpoint 2 at 0x4006db: file memoryshow.c, line 44.(gdb) cContinuing.堆p1                    0x6ab8b0堆p2                    0x6ab8d0棧&p3                   0x7fffffffe4d0棧&p2                   0x7fffffffe4d8棧s                     0x7fffffffe4e0棧&s[1]                 0x7fffffffe4e1棧&b                    0x7fffffffe4e4main地址                0×400494文本常量區              0x47f3b0全域初始化區            0x6a6e50(靜態)初始化區        0x6a6e54全域未初始化區          0x6a9ba8(靜態)未初始化區      0x6a6e58(靜態)未初始化區      0x6a6e5c(靜態)未初始化區      0x6a6e60USER=0x7fffffffedffARGC    0x7fffffffe4ccARGV    0x7fffffffe5c8ARGV    0x7fffffffe81b Breakpoint 2, main (argc=1, argv=0x7fffffffe5c8) at memoryshow.c:4545      sleep(300);(gdb) x/30s 0x7fffffffedff0x7fffffffedff:  "/usr/local/maven/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/jrockit/bin:/usr/local/redis/bin:/usr/local/mysql/bin:/root/bin"0x7fffffffee9c:  "MAIL=/var/spool/mail/root"0x7fffffffeeb6:  "nodeName=_0_218"0x7fffffffeec6:  "_=/usr/bin/gdb"0x7fffffffeed5:  "PWD=/root"0x7fffffffeedf:  "JAVA_HOME=/usr/local/jrockit"0x7fffffffeefc:  "LANG=en_US.UTF-8"0x7fffffffef0d:  "configDir=/data/config"0x7fffffffef24:  "LINES=43"0x7fffffffef2d:  "HISTCONTROL=ignoredups"0x7fffffffef44:  "HOME=/root"0x7fffffffef4f:  "SHLVL=1"0x7fffffffef57:  "M2_HOME=/usr/local/maven"0x7fffffffef70:  "LOGNAME=root"0x7fffffffef7d:  "SSH_CONNECTION=192.168.0.158 62555 192.168.0.98 22"0x7fffffffefb0:  "LESSOPEN=|/usr/bin/lesspipe.sh %s"0x7fffffffefd2:  "G_BROKEN_FILENAMES=1"0x7fffffffefe7:  "/root/memoryshow"   <–注意這裡0x7fffffffeff8:  ""0x7fffffffeff9:  ""0x7fffffffeffa:  ""0x7fffffffeffb:  ""0x7fffffffeffc:  ""0x7fffffffeffd:  ""0x7fffffffeffe:  ""0x7fffffffefff:  ""0x7ffffffff000:  <Address 0x7ffffffff000 out of bounds>0x7ffffffff000:  <Address 0x7ffffffff000 out of bounds>0x7ffffffff000:  <Address 0x7ffffffff000 out of bounds>0x7ffffffff000:  <Address 0x7ffffffff000 out of bounds>
 這裡可以看到程式啟動並執行絕對路徑在envstring中儲存,而envstring卻在棧中,而棧地址是每次運行都變化的。而且如果繼續測試,還可以發現,調用的方式不同,這個絕對路徑對應棧開始地址的位置也是不同。而且機器不同,演算法也不同。所以直接從進程記憶體裡面擷取絕對路徑也不太靠譜。 從/proc/self/exe擷取肚腩在網上查了下,最常用的方法是利用進程對應檔,用readlink讀取/proc/self/exe這個符號連結指向的檔案.插一段,如何判斷連結是符號連結或者是永久連結,主要是看檔案的INODE,一樣就是永久連結,不一樣就是符號連結。用ls -li查看。網上找到的一段代碼:
#include <stdio.h>#include <stdlib.h> #include <unistd.h> #define MAXBUFSIZE 1024 int main ( int argc, char * argv[] ) {     char buf[ MAXBUFSIZE ];     int count;     count = readlink( "/proc/self/exe", buf, MAXBUFSIZE );     if ( count < 0 || count >= MAXBUFSIZE ) {     printf( "Failed\n" );     return( EXIT_FAILURE );     }      buf[ count ] = '\0';     printf( "/proc/self/exe -> [%s]\n", buf );     return( EXIT_SUCCESS ); } /* end of main */ 
 exe檔案許可權問題可能有人會擔心/proc/self/exe這個檔案的許可權問題,肚腩覺得不需要擔心,該目錄下的檔案許可權是由系統確認的,使用者是不能修改的。因此讀的是自己進程在PROC目錄下的映射目錄,使用者權限肯定是有的。如果去讀其它使用者啟動並執行程式在/proc下的映射目錄,就不一定有許可權了。 

備忘:文章內容均出自肚腩照明月個人的真實理解以及對網上一些知識的總結,而並非存心妄自揣測來故意愚人耳目。由於個人水平有限,雖力求內容正確無誤,但仍然難免出錯,請勿見怪,如果可以則請留言告之。

 

http://www.wjthink.org/?p=522

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.