C - /proc/pid/exe 上的 Lstat

Posted

技术标签:

【中文标题】C - /proc/pid/exe 上的 Lstat【英文标题】:C - Lstat on /proc/pid/exe 【发布时间】:2014-07-05 00:22:10 【问题描述】:

我正在尝试使用 lstat 获取 /proc/pid/exe 文件的字节大小。这是我的代码:

int     main(int argc, char *argv[]) 
   
 struct stat    sb;
 char       *linkname;
 ssize_t    r;

  if (argc != 2) 
  
    fprintf(stderr, "Usage: %s <pathname>\n", argv[0]);
    exit(EXIT_FAILURE);
  

  if (lstat(argv[1], &sb) == -1) 
  
    perror("lstat");
    exit(EXIT_FAILURE);
  

  printf("sb.st_size %d\n", sb.st_size);   

  exit(EXIT_SUCCESS);

似乎 sb.st_size 总是等于 0,我不明白为什么。另外,此示例摘自 readlink(2) 手册页。

编辑:我正在尝试让它在 openSUSE 上运行。

提前感谢您的帮助。

【问题讨论】:

lstat 返回符号链接的大小。由于/proc/pid/exe 是一个链接,而不是一个文件,它的大小没有定义,零是一个合理的答案。如果您想要可执行文件的大小,请使用stat 这是我在这个男人身上读到的:The st_size field gives the size of the file (if it is a regular file or a symbolic link) in bytes.The size of a symbolic link is the length of the pathname it contains, without a terminating null byte. 这正是我要找的。它指向的路径名的长度。 这完全取决于操作系统。您没有指定操作系统。 抱歉,现在指定了。 啊!这里有一些东西可以尝试。 st_size 可能是一个 64 位的数量。在printf 中使用%llu 说明符。 【参考方案1】:

/proc 中的文件不是普通文件。对于他们中的大多数人来说,stat() 等。返回.st_size == 0

特别是,/proc/PID/exe 不是真正的符号链接或硬链接,而是一个特殊的伪文件,其行为大部分类似于符号链接。

(如果需要,您可以通过检查.st_dev 字段来检测procfs 文件。例如,与从lstat("/proc/self/exe",..) 获得的.st_dev 进行比较。)

要根据 PID 获取特定可执行文件的路径,我推荐一种依赖 readlink() 的返回值的方法:

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdio.h>

/* Creative Commons CC0: Public Domain dedication
 * (In jurisdictions without public domain, this example program
 *  is licensed under the Creative Commons CC0 license.)
 *
 * To the extent possible under law, Nominal Animal has waived all
 * copyright and related or neighboring rights to this example program.
 *
 * In other words, you are free to use it in any way you wish,
 * but if it breaks something, you get to keep all the pieces.
*/

/** exe_of() - Obtain the executable path a process is running
 * @pid: Process ID
 * @sizeptr: If specified, the allocated size is saved here
 * @lenptr: If specified, the path length is saved here
 * Returns the dynamically allocated pointer to the path,
 * or NULL with errno set if an error occurs.
*/
char *exe_of(const pid_t pid, size_t *const sizeptr, size_t *const lenptr)

    char   *exe_path = NULL;
    size_t  exe_size = 1024;
    ssize_t exe_used;
    char    path_buf[64];
    int     path_len;

    path_len = snprintf(path_buf, sizeof path_buf, "/proc/%ld/exe", (long)pid);
    if (path_len < 1 || path_len >= sizeof path_buf) 
        errno = ENOMEM;
        return NULL;
    

    while (1) 

        exe_path = malloc(exe_size);
        if (!exe_path) 
            errno = ENOMEM;
            return NULL;
        

        exe_used = readlink(path_buf, exe_path, exe_size - 1);
        if (exe_used == (ssize_t)-1)
            return NULL;

        if (exe_used < (ssize_t)1) 
            /* Race condition? */
            errno = ENOENT;
            return NULL;
        

        if (exe_used < (ssize_t)(exe_size - 1))
            break;

        free(exe_path);
        exe_size += 1024;
    

    /* Try reallocating the exe_path to minimum size.
     * This is optional, and can even fail without
     * any bad effects. */
    
        char *temp;

        temp = realloc(exe_path, exe_used + 1);
        if (temp) 
            exe_path = temp;
            exe_size = exe_used + 1;
        
    

    if (sizeptr)
        *sizeptr = exe_size;

    if (lenptr)
        *lenptr = exe_used;

    exe_path[exe_used] = '\0';
    return exe_path;


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

    int   arg;
    char *exe;
    long  pid;
    char  dummy;

    if (argc < 2 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")) 
        printf("\n");
        printf("Usage: %s [ -h | --help ]\n", argv[0]);
        printf("       %s PID [ PID ... ]\n", argv[0]);
        printf("\n");
        return 0;
    

    for (arg = 1; arg < argc; arg++)
        if (sscanf(argv[arg], " %ld %c", &pid, &dummy) == 1 && pid > 0L) 
            exe = exe_of((pid_t)pid, NULL, NULL);
            if (exe) 
                printf("Process %ld runs '%s'.\n", pid, exe);
                free(exe);
             else
                printf("Process %ld: %s.\n", pid, strerror(errno));
         else 
            printf("%s: Invalid PID.\n", argv[arg]);
            return 1;
        

    return 0;

在上面,exe_of() 函数返回伪符号链接/proc/PID/exe 指向的动态分配的副本,还可以选择存储分配的大小和/或路径长度。 (上面的示例程序不需要它们,因此它们为 NULL。)

这个想法很简单:分配一个初始动态指针,对于大多数情况来说足够大,但不会大得离谱。为字符串结尾的 NUL 字节保留最后一个字节。如果readlink() 返回的大小与给定的缓冲区长度相同——它本身不添加终止字符串结尾的 NUL 字节——那么缓冲区可能太短了;丢弃它,分配一个更大的缓冲区,然后重试。

同样,如果你想读取/proc/下的伪文件的全部内容,你不能先使用lstat()/stat()来找出你可能需要多大的缓冲区;您需要分配一个缓冲区,尽可能多地读取,并在必要时重新分配一个更大的缓冲区。 (我也可以展示示例代码。)

问题?

【讨论】:

好的,这几乎是完美的。我现在基本明白了,非常感谢。但是,您的示例代码的某些部分我没有得到。我认为你在 malloc 之后犯了一个错误,你可能没有检查正确的变量。在“while”循环之后会发生什么?我看到了花括号,但之前什么都没有。 @Amina:你说得对,很好!检查的是 malloc() 返回值;现在修好了。 while (1) .. 后面的块就是一个本地范围;你可以这样做。它只执行一次,你不能break 退出它,因为它不是一个循环,只是一个代码块。我为它使用了一个单独的块,因为它有点特殊:它将字符串重新分配到最佳大小,但即使它失败,也不会发生任何不好的事情,字符串只是占用了不必要的内存。通常,我会在它之前发表评论。其实我现在就加进去。 谢谢你。你帮了大忙! 另请注意:如果链接的路径> PATH_MAX 在Linux上,这仍然会失败,即使文件在链接中是有效的 .. 到fs/proc/base.c:do_proc_readlink(),如果-ENAMETOOLONG 具有单页缓冲区,并且用户提供的缓冲区更长。内核开发人员目前认为这是不必要的,因为高阶分配是有问题的。 path walk 本身很容易竞争,并且不能通过将组件直接写入用户空间缓冲区来完成。如果你愿意测试,我可以为你提供一个内核补丁,你可以用它来验证。【参考方案2】:

无法对我要回复的帖子发表评论,非常抱歉!

Valgrind 将在 Nominal Animal 的帖子中报告 exe_path = malloc(exe_size); 为“块肯定丢失”。所以,如果你使用那个功能,那么你的记忆力会很快上升。

即使您返回 NULL 也释放内存将解决此问题。还应添加从 malloc 调用中显式转换 (char*) 以使 gcc 在您使用 -Wall 时停止抱怨

char* exe_of(const pid_t pid, size_t *const sizeptr, size_t *const lenptr)

    char   *exe_path = NULL;
    size_t  exe_size = 1024;
    ssize_t exe_used;
    char    path_buf[64];
    unsigned int path_len;

    path_len = snprintf(path_buf, sizeof path_buf, "/proc/%ld/exe", (long)pid);
    if (path_len < 1 || path_len >= sizeof path_buf) 
        errno = ENOMEM;
        return NULL;
    

    while (1) 

        exe_path = (char*)malloc(exe_size);
        if (!exe_path) 
            errno = ENOMEM;
            return NULL;
        

        exe_used = readlink(path_buf, exe_path, exe_size - 1);
        if (exe_used == (ssize_t)-1) 
            free(exe_path);
            return NULL;
        

        if (exe_used < (ssize_t)1) 
            /* Race condition? */
            errno = ENOENT;
            free(exe_path);
            return NULL;
        

        if (exe_used < (ssize_t)(exe_size - 1))
            break;

        free(exe_path);
        exe_size += 1024;
    

    /* Try reallocating the exe_path to minimum size.
     * This is optional, and can even fail without
     * any bad effects. */
    
        char *temp;

        temp = (char*)realloc(exe_path, exe_used + 1);
        if (temp) 
            exe_path = temp;
            exe_size = exe_used + 1;
        
    

    if (sizeptr)
        *sizeptr = exe_size;

    if (lenptr)
        *lenptr = exe_used;

    exe_path[exe_used] = '\0';
    return exe_path;

【讨论】:

【参考方案3】:

通常您不必担心这一点,但是在使用 Nominal Animal 发表评论后,似乎 linux 将 /proc/PID/exe 的文件路径限制为 PAGE_SIZE。因此,即使文件系统支持比这更长的路径,也无法让 readlink() 为您提供该路径,因为这是作为硬限制实现的。我找到了另一种方法,因此如果读取链接因 ENAMETOOLONG 失败,您可以读取 /proc/PID/maps,尽管这可能会被命中或错过,但我发现它会命中 4 个不同发行版中的每个进程,除了没有的系统进程实际文件名 (ENOENT)

int getExecutableFromMaps(char *buf, size_t bufsize) 
  FILE *fp;
  char *mylinebuf = NULL;
  size_t mylinebufsize = 0;
  size_t counter = 0;
  size_t start = 0;
  size_t column = 0;
  int result = -1;
  fp = fopen("/proc/self/maps", "r");
  if( fp != NULL ) 
    if( getline(&mylinebuf, &mylinebufsize, fp) >= 0 ) 
      if( mylinebuf != NULL ) 
        while( column < 5 && counter < mylinebufsize ) 
          while( counter < mylinebufsize && mylinebuf[counter] != ' ') 
            counter++;
          
          while( counter < mylinebufsize && mylinebuf[counter] == ' ') 
            counter++;
          
          column++;
        
        int start = counter;
        while( counter < mylinebufsize && (mylinebuf[counter] != '\n' && mylinebuf[counter] != '\r') ) 
          counter++;
        
        if( counter <= mylinebufsize && start < counter && (counter-start+1)<=bufsize ) 
          memcpy(buf, &mylinebuf[start], counter-start);
          buf[counter-start+1] = 0;
          result = counter-start+1;
        
      
    
    if( mylinebuf != NULL ) 
      free(mylinebuf);
    
    fclose(fp);
  
  return result;

注意:这是补充代码,只能在 readlink 失败后调用,因为这很昂贵。

【讨论】:

这是一个非常好的观点。就个人而言,我更愿意将我们两者组合成一个函数,在给定进程 PID 的情况下,将可执行路径作为动态分配的指针返回。您介意我从您那里窃取方法,并将合并后的新版本添加到我的答案中吗? (当然,有适当的归属。我有点惭愧,我自己没有想到这种方法——我已经发布了一些关于parsin /proc/PID/maps 等的答案——所以我确实知道,而且我我想纠正它。) 是的,继续游戏 注意:我创建了一个超过 PAGE_SIZE 的目录,并将可执行文件放入其中进行测试。只有 *maps(maps,numa_maps, and smaps) 文件的路径正确且没有错误。使用EXT4,但我测试XFS也没有路径长度限制,如果需要会尝试BTRFS。还需要注意的是,路径已解析,因此不需要调用 realpath()。 糟糕。我在 fs/proc/task_mmu.c 中发现了一个错误,它导致名为 "some\nname""some\\012name" 的文件在 proc 映射文件中都显示为 some\012name。那是 nasty - 真的允许隐藏可执行文件。我将首先为此向上游推送修复程序,稍后再讨论。我想我还会就“修复”proc readlink() 上的页面长度限制征求意见——也可以将其转换为 seq 接口,避免预先分配的固定缓冲区。如果一周左右没有任何事情发生。 :) 祝您好运尝试修复 readlink。因为这是定义标准的方式。但也许你可以让他们看到安全方面胜过使用标准。可能会更容易建议一个新的 api 调用来满足需要。我也希望单个文件名的最大大小大于 63 个字符 :(。认为我应该向内核 bugzilla 提交一个错误?

以上是关于C - /proc/pid/exe 上的 Lstat的主要内容,如果未能解决你的问题,请参考以下文章

在linux下通过啥命令可以知道相应PID的路径

linux 查看进程的bin文件所在路径

linux病毒查杀

在列表中包含的值中定义固定位置

Android 平台上的原生 C/C++ 代码

Visual C/C++ 2010 上的 LAPACK