电脑爱好者,提供IT资讯信息及各类编程知识文章介绍,欢迎大家来本站学习电脑知识。 最近更新 | 联系我们 RSS订阅本站最新文章
电脑爱好者
站内搜索: 
当前位置:首页>> Linux专题>>Linux下编程获取应用程序绝对路径:

Linux下编程获取应用程序绝对路径

来源:linuxforum | 2007-3-9 | (有4162人读过)


经常看到有人提问在linux中如何获取当前应用程序的绝对路径, 却很少有人能比较好的解答. 现转贴一篇文章, 希望能对受这个问题困扰的人有帮助.  

13.12 如何获取当前进程对应之静态映像文件的绝对路径  
A: hushui110@水木清华  
这是一个x86/Linux Kernel 2.4.7-10系统中利用proc获取绝对路径的例子  
--------------------------------------------------------------------------  
/*  

* gcc -Wall -pipe -g -static -o myprog_2 myprog_2.c  

*/  

#include  

#include  

#include  



#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 */  


--------------------------------------------------------------------------  

[scz@ /home/scz/src]> echo $PATH  
/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:.  
[scz@ /home/scz/src]> ./myprog_2  
/proc/self/exe -> [/home/scz/src/myprog_2]  
[scz@ /home/scz/src]> ../src/myprog_2  
/proc/self/exe -> [/home/scz/src/myprog_2]  
[scz@ /home/scz/src]> myprog_2  
/proc/self/exe -> [/home/scz/src/myprog_2]  
[scz@ /home/scz/src]>  

显然这里直接给出了最期待的结果,没有冗余信息。  

A: scz & microcat  
2000-03-18  

下面在x86/Linux Kernel 2.4.7-10上演示、讨论  

--------------------------------------------------------------------------  

*  

* gcc -Wall -pipe -g -static -o myprog myprog.c  

*/  

#include  

#include  



int main ( int argc, char * argv[] )  

{  

return( EXIT_SUCCESS );  

} /* end of main */  


--------------------------------------------------------------------------  

[scz@ /home/scz/src]> gcc -Wall -pipe -g -static -o myprog myprog.c  
[scz@ /home/scz/src]> echo $PATH  
/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:.  
[scz@ /home/scz/src]> gdb ./myprog  
(gdb) b main  
(gdb) r  
(gdb) x/17s 0xbfffff00  
0xbfffff00: "SHLVL=1"  
0xbfffff08: "_=/bin/bash"  
0xbfffff14: "SHELL=/bin/bash"  
0xbfffff24: "HOSTTYPE=i386"  
0xbfffff32: "OSTYPE=linux-gnu"  
0xbfffff43: "HISTSIZE=1000"  
0xbfffff51: "TERM=vt100"  
0xbfffff5c: "HOME=/home/scz"  
0xbfffff6b: "SSH_ASKPASS=/usr/libexec/openssh/gnome-ssh-askpass"  
0xbfffff9e: "PATH=/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:."  
0xbfffffd2: "LESSCHARSET=latin1"  
0xbfffffe5: "/home/scz/src/./myprog" <-- 注意这个输出  
0xbffffffc: ""  
0xbffffffd: ""  
0xbffffffe: ""  
0xbfffffff: ""  
0xc0000000:  
[scz@ /home/scz/src]> gdb myprog  
(gdb) b main  
(gdb) r  
(gdb) x/17s 0xbfffff00  
0xbfffff00: "z"  
0xbfffff02: "SHLVL=1"  
0xbfffff0a: "_=/bin/bash"  
0xbfffff16: "SHELL=/bin/bash"  
0xbfffff26: "HOSTTYPE=i386"  
0xbfffff34: "OSTYPE=linux-gnu"  
0xbfffff45: "HISTSIZE=1000"  
0xbfffff53: "TERM=vt100"  
0xbfffff5e: "HOME=/home/scz"  
0xbfffff6d: "SSH_ASKPASS=/usr/libexec/openssh/gnome-ssh-askpass"  
0xbfffffa0: "PATH=/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:."  
0xbfffffd4: "LESSCHARSET=latin1"  
0xbfffffe7: "/home/scz/src/myprog" <-- 注意这个输出  
0xbffffffc: ""  
0xbffffffd: ""  
0xbffffffe: ""  
0xbfffffff: ""  
[scz@ /home/scz/src]> gdb ../src/myprog  
(gdb) b main  
(gdb) r  
(gdb) x/17s 0xbfffff00  
0xbfffff00: "=1"  
0xbfffff03: "_=/bin/bash"  
0xbfffff0f: "SHELL=/bin/bash"  
0xbfffff1f: "HOSTTYPE=i386"  
0xbfffff2d: "OSTYPE=linux-gnu"  
0xbfffff3e: "HISTSIZE=1000"  
0xbfffff4c: "TERM=vt100"  
0xbfffff57: "HOME=/home/scz"  
0xbfffff66: "SSH_ASKPASS=/usr/libexec/openssh/gnome-ssh-askpass"  
0xbfffff99: "PATH=/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:."  
0xbfffffcd: "LESSCHARSET=latin1"  
0xbfffffe0: "/home/scz/src/../src/myprog" <-- 注意这个输出  
0xbffffffc: ""  
0xbffffffd: ""  
0xbffffffe: ""  
0xbfffffff: ""  
0xc0000000:  
[scz@ /home/scz/src]>  

这是ELF文件在Linux系统中加载进内存之后的布局简图  

--------------------------------------------------------------------------  
0x08048000 code .text,代码,只读  
data .data,包含已经初始化的数据,只读  
bss .bss,未初始化数据,初始化成0,读/写  
... 堆区,动态分配获取的内存从.bss往内存高端增长  
... (heap),读/写  
...  
stack 栈区,起始地址大于0xBFFF0000  
arguments main()的形参  
environment 环境变量区域  
program name execve()第一形参,不是argv[0]  
0xBFFFFFFC null(dword) 最后四个字节固定为零  
0xC0000000  
--------------------------------------------------------------------------  

通常动态链接库被映射到0x40000000往高端的地址。对于Linux,如果打了不可执行  
堆栈内核补丁,动态链接库被映射到0x40000000往低端的地址。tt说就是以前的映射  
地址减去0x40000000。打了补丁后使得通过字符串拷贝(strcpy)传递shellcode相对  
复杂化,需要更多技巧。  

program name处不一定是绝对路径,实际对应了execve()第一形参。一般从shell上  
启动进程,shell根据PATH环境变量自动搜索匹配出一个路径,未必是绝对路径。假  
设PATH环境变量中有当前目录(.),所执行的程序也只在当前目录下有,直接指定程  
序名(myprog)执行时,shell会向execve()第一形参传递"./myprog"。注意,execve  
第一形参未必是绝对路径。用gdb加载后再执行,情况有所不同。如果gdb命令行上指  
定的程序名非绝对路径,gdb在调用execve()之前会调用getcwd()拼接在程序名之前,  

此时不依赖PATH环境变量。所以用gdb调试溢出程序时应该在命令行上指定绝对路径,  

避免不必要的偏移调整。  

现在来看这样一个演示程序  

--------------------------------------------------------------------------  

/*  

* gcc -Wall -pipe -g -static -o myprog_1 myprog_1.c  

*/  

#include  

#include  



extern char **environ;  



int main ( int argc, char * argv[] )  

{  

char *path = ( char * )( 0xc0000000 - 5 );  



while ( *--path );  

++path;  

printf( "path --> %08x [%s]\n", ( unsigned int )path, path );  

printf( "argv --> %08x\n", ( unsigned int )argv );  

printf( "argv[0] --> %08x [%s]\n", ( unsigned int )argv[0],  
argv[0] );  

printf( "environ --> %08x\n", ( unsigned int )environ );  

printf( "environ[0] --> %08x [%s]\n", ( unsigned int )environ[0],  
environ[0] );  

return( EXIT_SUCCESS );  

} /* end of main */  


--------------------------------------------------------------------------  

直接执行,然后用gdb加载后执行,对比输出上的不同。  

[scz@ /home/scz/src]> myprog_1  
path --> bffffff1 [./myprog_1] <-- 注意这里前面多了一个"./"  
argv --> bffffb24  
argv[0] --> bffffc29 [myprog_1]  
environ --> bffffb2c  
environ[0] --> bffffc32 [PWD=/home/scz/src]  
[scz@ /home/scz/src]> ./myprog_1  
path --> bffffff1 [./myprog_1]  
argv --> bffffb24  
argv[0] --> bffffc27 [./myprog_1]  
environ --> bffffb2c  
environ[0] --> bffffc32 [PWD=/home/scz/src]  
[scz@ /home/scz/src]> ../src/myprog_1  
path --> bfffffec [../src/myprog_1]  
argv --> bffffb14  
argv[0] --> bffffc18 [../src/myprog_1]  
environ --> bffffb1c  
environ[0] --> bffffc28 [PWD=/home/scz/src]  
[scz@ /home/scz/src]>  

Linux系统中main()函数里自修改argv[0]欺骗ps有效,而FreeBSD、Solaris则无效。  

execve()第二形参可以指定不同于第一形参的argv[0]。argv[0]不可信,但execve()  
第一形参相对就可信得多。如果是自己写程序,可以考虑在main()中第一时刻判断  
execve()第一形参是否为相对路径,进而决定是否调用getcwd(),最后拼接出一个绝  
对路径。就像gdb所做的那样。  

需要考虑"/home/scz/src/./myprog"、"/home/scz/src/../src/myprog"这些情况,  
消掉"./"、"../"等冗余信息。  

A: scz & microcat  
2002-09-13 18:00  

下面在SARPC/Solaris 8 64-bit kernel mode上演示、讨论  

--------------------------------------------------------------------------  

/*  

* gcc -Wall -pipe -g -static -o myprog_3 myprog_3.c  

*/  

#include  

#include  



extern char **environ;  



static void outputBinary ( const unsigned char *byteArray, const size_t  
byteArrayLen )  

{  

size_t offset, k, j, i;  



fprintf( stderr, "byteArray [ %u bytes ] ----> \n", byteArrayLen );  

if ( byteArrayLen <= 0 )  

{  

return;  

}  

i = 0;  

offset = 0;  

for ( k = byteArrayLen / 16; k > 0; k--, offset += 16 )  

{  

fprintf( stderr, "%08X ", offset );  

for ( j = 0; j < 16; j++, i++ )  

{  

if ( j == 8 )  

{  

fprintf( stderr, "-%02X", byteArray[ i ] );  

}  

else  

{  

fprintf( stderr, " %02X", byteArray[ i ] );  

}  

}  

fprintf( stderr, " " );  

i -= 16;  

for ( j = 0; j < 16; j++, i++ )  

{  

/*  

* if ( isprint( (int)byteArray[ i ] ) )  

*/  

if ( ( byteArray[ i ] >= ’ ’ ) && ( byteArray[ i ]  
<= 255 )  

&& ( byteArray[ i ] != 0x7f ) )  

{  

fprintf( stderr, "%c", byteArray[ i ] );  

}  

else  

{  

fprintf( stderr, "." );  

}  

}  

fprintf( stderr, "\n" );  

} /* end of for */  

k = byteArrayLen - i;  

if ( k <= 0 )  

{  

return;  

}  

fprintf( stderr, "%08X ", offset );  

for ( j = 0 ; j < k; j++, i++ )  

{  

if ( j == 8 )  

{  

fprintf( stderr, "-%02X", byteArray[ i ] );  

}  

else  

{  

fprintf( stderr, " %02X", byteArray[ i ] );  

}  

}  

i -= k;  

for ( j = 16 - k; j > 0; j-- )  

{  

fprintf( stderr, " " );  

}  

fprintf( stderr, " " );  

for ( j = 0; j < k; j++, i++ )  

{  

if ( ( byteArray[ i ] >= ’ ’ ) && ( byteArray[ i ] <=  
255 )  

&& ( byteArray[ i ] != 0x7f ) )  

{  

fprintf( stderr, "%c", byteArray[ i ] );  

}  

else  

{  

fprintf( stderr, "." );  

}  

}  

fprintf( stderr, "\n" );  

return;  

} /* end of outputBinary */  



int main ( int argc, char * argv[] )  

{  

char *path = ( char * )( 0xffbf0000 - 5 );  



while ( *path == ’\0’ )  

{  

path--;  

}  

while ( *path )  

{  

path--;  

}  

++path;  

printf( "path --> %08x [%s]\n", ( unsigned int )path, path );  

printf( "argv --> %08x\n", ( unsigned int )argv );  

printf( "argv[0] --> %08x [%s]\n", ( unsigned int )argv[0],  
argv[0] );  

printf( "environ --> %08x\n", ( unsigned int )environ );  

printf( "environ[0] --> %08x [%s]\n", ( unsigned int )environ[0],  
environ[0] );  

outputBinary( ( const unsigned char * )( 0xffbf0000 - 256 ), 256 );  

return( EXIT_SUCCESS );  

} /* end of main */  


--------------------------------------------------------------------------  

直接执行,然后用gdb加载后执行,对比输出上的不同。SPARC/Solaris与x86/Linux  
在此问题上有不少区别。  

[scz@ /home/scz/src]> echo $PATH  
/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:.  
[scz@ /export/home/scz/src]> myprog_3  
path --> ffbeffee [myprog_3]  
argv --> ffbefcfc  
argv[0] --> ffbefd7c [myprog_3]  
environ --> ffbefd04  
environ[0] --> ffbefd85 [PWD=/export/home/scz/src]  
[scz@ /export/home/scz/src]> ./myprog_3  
path --> ffbefff0 [myprog_3] <-- 注意这里前面反而没有了"./"  
argv --> ffbefcfc  
argv[0] --> ffbefd7c [./myprog_3]  
environ --> ffbefd04  
environ[0] --> ffbefd87 [PWD=/export/home/scz/src]  
[scz@ /export/home/scz/src]> ../src/myprog_3  
path --> ffbeffea [../src/myprog_3]  
argv --> ffbefcec  
argv[0] --> ffbefd6c [../src/myprog_3]  
environ --> ffbefcf4  
environ[0] --> ffbefd7c [PWD=/export/home/scz/src]  
[scz@ /export/home/scz/src]>  

如果我们复制myprog_3到/usr/bin下去,再次执行myprog_3,可以看到如下输出  

[scz@ /export/home/scz/src]> myprog_3  
path --> ffbeffe9 [/usr/bin/myprog_3] <-- 注意这里  
argv --> ffbefcf4  
argv[0] --> ffbefd74 [myprog_3]  
environ --> ffbefcfc  
environ[0] --> ffbefd7d [PWD=/export/home/scz/src]  
byteArray [ 256 bytes ] ---->  
00000000 55 6C 74 72 61 2D 35 5F-31 30 00 53 48 4C 56 4C Ultra-5_10.SHLVL  
00000010 3D 31 00 5F 49 4E 49 54-5F 55 54 53 5F 4D 41 43 =1._INIT_UTS_MAC  
00000020 48 49 4E 45 3D 73 75 6E-34 75 00 53 48 45 4C 4C HINE=sun4u.SHELL  
00000030 3D 2F 62 69 6E 2F 62 61-73 68 00 48 4F 53 54 54 =/bin/bash.HOSTT  
00000040 59 50 45 3D 73 70 61 72-63 00 4F 53 54 59 50 45 YPE=sparc.OSTYPE  
00000050 3D 73 6F 6C 61 72 69 73-32 2E 38 00 48 4F 4D 45 =solaris2.8.HOME  
00000060 3D 2F 65 78 70 6F 72 74-2F 68 6F 6D 65 2F 73 63 =/export/home/sc  
00000070 7A 00 54 45 52 4D 3D 76-74 31 30 30 00 50 41 54 z.TERM=vt100.PAT  
00000080 48 3D 2F 62 69 6E 3A 2F-75 73 72 2F 62 69 6E 3A H=/bin:/usr/bin:  
00000090 2F 73 62 69 6E 3A 2F 75-73 72 2F 73 62 69 6E 3A /sbin:/usr/sbin:  
000000A0 2F 75 73 72 2F 6C 6F 63-61 6C 2F 62 69 6E 3A 2E /usr/local/bin:.  
000000B0 00 5F 49 4E 49 54 5F 4E-45 54 5F 53 54 52 41 54 ._INIT_NET_STRAT  
000000C0 45 47 59 3D 6E 6F 6E 65-00 5F 3D 2F 62 69 6E 2F EGY=none._=/bin/  
000000D0 6D 79 70 72 6F 67 5F 33-00 53 55 4E 57 2C 55 6C myprog_3.SUNW,Ul  
000000E0 74 72 61 2D 35 5F 31 30-00 2F 75 73 72 2F 62 69 tra-5_10./usr/bi  
000000F0 6E 2F 6D 79 70 72 6F 67-5F 33 00 00 00 00 00 00 n/myprog_3......  
[scz@ /export/home/scz/src]>  

前面说的getcwd()拼接技术同样适用于SPARC/Solaris 8。  

与Linux相比,有几处重要变化,一是从0xC0000000变成0xFFBF0000,二是如果程序  
在当前目录下,没有了前面的"./",三是SPARC芯片的一些对齐特性导致我们要找的  
位置可能比想像的要向内存低端移动一些字节。  

后两点容易理解,那我们是如何确定0xC0000000、0xFFBF0000的呢。这个问题实际上  
就是<<如何编程获取栈底地址>>所解决的,参看这篇QA。假设堆栈(stac  
k)向低地址  
方向增长,则所谓栈底指堆栈(stack)最高地址,下面是tt用程序找出来的  

x86/Linux 栈底是0xc0000000( 栈底往低地址的4个字节总是零 )  
SPARC/Solaris 7/8 栈底是0xffbf0000( 栈底往低地址的4个字节总是零 )  
SPARC/Solaris 2.6 栈底是0xf0000000( 栈底往低地址的4个字节总是零 )  
x86/FreeBSD 栈底是0xbfc00000( 栈底往低地址的4个字节总是零 )  
x86/NetBSD 1.5 栈底是0xbfbfe000  
x86/OpenBSD 2.8 栈底是0xdfbfe000  

利用ELF文件加载进内存之后的布局获取绝对路径固然可行,但是带有很强的Hacking  
味,并不可取,不是万不得已不要使用这种技术。  

Linux有"/proc/self/exe",Solaris有"/proc/self/auxv",这些都是可以一用的。  
当然在Solaris系统中直接使用getexecname(3C)获取execve()第一形参最好,否则涉  
及在用户空间读取U区的问题,也带有很强的Kernel  
Hacking味道。参看<<如何在命  
令行上访问指定进程P、U两区,如何欺骗Solaris的ps>>、<  
e(3C)是怎么  
实现的>>两篇QA。  

D: law@水木清华  

在main()一开始就取execve()第一形参,或者退而求其次取argv[0],然后解析$PATH  
环境变量,自己确定哪个目录里含有进程静态文件映像。如果是取execve()第一形参?  
?  
这个办法可行。如果取argv[0],就面临着argv[0]不可信的问题。而前面的技术取的  
都是execve()第一形参。  

注意,如果取argv[0],就不能依赖getcwd()拼接,这点与取execve()第一形参不同。  

很可能命令行上的"myprog_3"实际根据$PATH产生了"/usr/bin/myprog_3",argv[0]  
与getcwd()无论如何也拼不出正确的绝对路径。  

A: scz 2002-09-13 20:45  

这是一个x86/FreeBSD 4.5-RELEASE系统中利用proc获取绝对路径的例子  

--------------------------------------------------------------------------  

/*  

* gcc -Wall -pipe -g -static -o myprog_4 myprog_4.c  

*/  

#include  

#include  

#include  

#include  



#define MAXBUFSIZE 1024  



int main ( int argc, char * argv[] )  

{  

char proc[64];  

char buf[ MAXBUFSIZE ];  

int count;  



sprintf( proc, "/proc/%d/file", ( unsigned int )getpid() );  

count = readlink( proc, buf, MAXBUFSIZE );  

if ( count < 0 || count >= MAXBUFSIZE )  

{  

printf( "Failed\n" );  

return( EXIT_FAILURE );  

}  

buf[ count ] = ’\0’;  

printf( "%s -> [%s]\n", proc, buf );  

return( EXIT_SUCCESS );  

} /* end of main */  


--------------------------------------------------------------------------  

[scz@ /home/scz/src]> echo $PATH  
/bin:/usr/bin:/sbin:/usr/sbin:/usr/local/bin:.  
[scz@ /home/scz/src]> myprog_4  
/proc/121/file -> [/usr/home/scz/src/myprog_4]  
[scz@ /home/scz/src]> ./myprog_4  
/proc/122/file -> [/usr/home/scz/src/myprog_4]  
[scz@ /home/scz/src]> ../src/myprog_4  
/proc/123/file -> [/usr/home/scz/src/myprog_4]  
[scz@ /home/scz/src]>  

与Linux系统中"/proc/self/exe"一样,FreeBSD系统中"/proc/ /file"直?  
痈?  
了最期待的结果,没有冗余信息。  

手头系统有限,无法一一验证其它Unix系统。个人觉得从栈底向低端移动取execve()  
第一形参的办法比较通用,可以写成可移植的函数,但还是不太推荐这种Hacking。  
Linux专题热门文章排行
网站赞助商
购买此位置

 

关于我们 | 网站地图 | 文档一览 | 友情链接| 联系我们

Copyright © 2003-2021 电脑爱好者 版权所有 备案号:鲁ICP备09059398号