如何在 C (MacOS) 中获取进程虚拟内存映射的信息

2024-02-22

我想要获取有关 C (MacOS) 中当前进程的进程虚拟内存布局(堆栈、libc、堆和代码)的信息。

我读到在 Linux 中你可以通过查看来做到这一点/proc/<pid>/maps您可以通过以下命令在 MacOS 命令行上执行此操作vmmap命令行实用程序。

我怎样才能在C中做到这一点?如果可能的话,我想获取与我的进程相关的虚拟内存空间中的所有页面。


您可以使用mach_vm_region_info用于从 C 查询此信息的 API。以下是来自:

#include <mach/vm_map.h>
#include <stdio.h>

#include <mach-o/dyld_images.h>

/**
 * vmmap(1) clone for OS X and iOS
 * -------------------------------
 *
 * This is a simple example of using the mach_vm_region_info APIs in order to 
 * obtain a process' (technically, a task's) virtual memory address space, in a
 * manner akin to /proc/[pid]/maps on Linux.
 *
 * The process is simple - get the task port, then call mach_vm_region_info until
 * you've exhausted the address space (in iOS this happens around 0x40000000, 
 * where the commpage is). On iOS 6, for some peculiar reason the task port is
 * invalidated after each call, so the quick workaround here solves the problem
 * by regetting the port. The actual mach error code to check for is in the header
 * files, though the code simply tries regetting.
 *
 * N.B - For this code to work, you MUST provide the entitlements to allow 
 * task-for-pid to work, else you'll fail with error 5. The entitlements are in 
 * the output in Chapter 3, but for those of you who haven't bought the book, it would be:
 *
--- Cut here 

<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
        <key>com.apple.springboard.debugapplications</key>
        <true/>
        <key>get-task-allow</key>
        <true/>
        <key>proc_info-allow</key>
        <true/>
        <key>task_for_pid-allow</key>
        <true/>
        <key>run-unsigned-code</key>
        <true/>
</dict>
</plist>

--- Ok, enough :-)
 *
 *  so - copy the above XML to a file, say, "ent.xml", and be sure to run "ldid -Sent.xml vmmap"
 *  before trying to run this. You can download the binary (already thus signed) if you're lazy
 *  (and trust me, because you *will* need root on your i-Device for this)
 *
 *  As the book clearly states, once you have the task port, the world is your oyster. You can
 *  control the entire virtual memory space, reading and writing it as you please. Stay tuned
 *  for the corrupt tool (which will be provided soon in binary form)
 *
 */

int g_pid = 0; // required in iOS 6 (read below)

 /* 03/08/13 - Added List of Mach-O images: */


struct dyld_image_info *g_dii = NULL;
int     g_imageCount;

unsigned char *
readProcessMemory (int pid, mach_vm_address_t addr, mach_msg_type_number_t *size)
{
    // Helper function to read process memory (a la Win32 API of same name)
    // To make it easier for inclusion elsewhere, it takes a pid, and
    // does the task_for_pid by itself. Given that iOS invalidates task ports
    // after use, it's actually a good idea, since we'd need to reget anyway

    task_t  t;
    task_for_pid(mach_task_self(),pid, &t);
        mach_msg_type_number_t  dataCnt = size;
        vm_offset_t readMem;

    // Use vm_read, rather than mach_vm_read, since the latter is different
    // in iOS.

        kern_return_t kr = vm_read(t,        // vm_map_t target_task,
                     addr,     // mach_vm_address_t address,
                     *size,     // mach_vm_size_t size
                     &readMem,     //vm_offset_t *data,
                     size);     // mach_msg_type_number_t *dataCnt

        if (kr) {
                // DANG..
                fprintf (stderr, "Unable to read target task's memory @%p - kr 0x%x\n" , addr, kr);
                 return NULL;
                }

    return ( (unsigned char *) readMem);

}



kern_return_t mach_vm_read (vm_map_t, mach_vm_address_t, mach_vm_size_t, vm_offset_t *, mach_msg_type_number_t *);
void 
findListOfBinaries(task_t  t, mach_vm_address_t    addr, int size)
{


        kern_return_t kr;
        mach_msg_type_number_t  dataCnt = size;

    unsigned char *readData = readProcessMemory (g_pid, addr, &dataCnt);

    int machsig = 0xfeedface;

    // Checking only 0xfeedfa is a simple way to catch both 64-bit (facf) and 32-bit (face) headers
        // Machine endianness is automatically taken care of, too..

    if (readData && memcmp (readData + 1, ((unsigned char *) &machsig) + 1 , 3) == 0)
    {
        // This is a Mach header
        int i = 0;


        // A MUCH better way would be to iterate through the LC and find the name of dyld
        // but this would require my machlib.c (closed source) and really get the same result.
        // This works because on both iOS and OS X dyld is at /usr/lib.

        for (i = 0; i <dataCnt; i++)
        {
            if (memcmp(readData+i, "lib/dyld", 8) == 0)
            {
                unsigned int dyld_all_image_infos_offset ;
                int imageCount = 0;

                memcpy (&dyld_all_image_infos_offset, readData+DYLD_ALL_IMAGE_INFOS_OFFSET_OFFSET, sizeof (unsigned int));


                 struct dyld_all_image_infos *dyldaii ;
                 // Safeguard: should check that dyld_all_image_infos_offset is < size..

                if (dyld_all_image_infos_offset > size)
                {
                    // This is to be expected, since the dyld_all_image_infos is in a data region

                    //printf ("Offset %x is greater than region size : %x\n", dyld_all_image_infos_offset, size);
                    dataCnt = sizeof(dyld_all_image_infos);
                    readData = readProcessMemory (g_pid, addr + dyld_all_image_infos_offset , &dataCnt);

                    if (!readData) { return;}
                    dyldaii = (struct dyld_all_image_infos *) readData;

                }
                else
                {
                    dyldaii = (struct dyld_all_image_infos *) (readData +dyld_all_image_infos_offset);
                }


                printf ("Version: %d, %d images at offset %p\n",
                        dyldaii->version, dyldaii->infoArrayCount, dyldaii->infoArray);

                // Go to dyldaii->infoArray address

                imageCount = dyldaii->infoArrayCount;
                dataCnt = imageCount * sizeof(struct dyld_image_info);
                g_dii = (struct dyld_image_info *) malloc (dataCnt);
                g_imageCount = imageCount;
                readData = readProcessMemory(g_pid, dyldaii->infoArray, &dataCnt);
                if (!readData) { return;}

                struct dyld_image_info *dii = (struct dyld_image_info *) readData;

                // We don't need i anymore, anyway
                for (i = 0; i < imageCount; i++)
                    {
                        dataCnt = 1024;
                        char *imageName = readProcessMemory (g_pid, dii[i].imageFilePath, &dataCnt);
                        if (imageName) g_dii[i].imageFilePath = strdup(imageName);
                        else g_dii[i].imageFilePath = NULL;
                        g_dii[i].imageLoadAddress = dii[i].imageLoadAddress;
                    }

                break;
            }

        }
    }

}



 /* End 03/08/13 */
char *
behavior_to_text (vm_behavior_t b)
{

  switch (b)
    {
        case VM_BEHAVIOR_DEFAULT: return("default");
        case VM_BEHAVIOR_RANDOM:  return("random");
        case VM_BEHAVIOR_SEQUENTIAL: return("fwd-seq");
        case VM_BEHAVIOR_RSEQNTL: return("rev-seq");
        case VM_BEHAVIOR_WILLNEED: return("will-need");
        case VM_BEHAVIOR_DONTNEED: return("will-need");
        case VM_BEHAVIOR_FREE: return("free-nowb");
        case VM_BEHAVIOR_ZERO_WIRED_PAGES: return("zero-wire");
        case VM_BEHAVIOR_REUSABLE: return("reusable");
        case VM_BEHAVIOR_REUSE: return("reuse");
        case VM_BEHAVIOR_CAN_REUSE: return("canreuse");
        default: return ("?");
    }


}
char *
protection_bits_to_rwx (vm_prot_t p)
{

  // previous version of this somehow lost the "p&", always returning rwx..
  static char returned[4];

  returned[0] = (p &VM_PROT_READ    ? 'r' : '-');
  returned[1] = (p &VM_PROT_WRITE   ? 'w' : '-');
  returned[2] = (p & VM_PROT_EXECUTE ? 'x' : '-');
  returned[3] = '\0';

 // memory leak here. No biggy
  return (strdup(returned));

}

const char *
unparse_inheritance (vm_inherit_t i)
{
  switch (i)
    {
    case VM_INHERIT_SHARE:
      return "share";
    case VM_INHERIT_COPY:
      return "copy";
    case VM_INHERIT_NONE:
      return "none";
    default:
      return "???";
    }
}

macosx_debug_regions (task_t task, mach_vm_address_t address, int max)
{
  kern_return_t kret;

  mach_vm_address_t prev_address;
  /* @TODO: warning - potential overflow here - gotta fix this.. */
  vm_region_basic_info_data_t prev_info,info;
  mach_vm_size_t size, prev_size;

  mach_port_t object_name;
  mach_msg_type_number_t count;

  int nsubregions = 0;
  int num_printed = 0;

  count = VM_REGION_BASIC_INFO_COUNT_64;
  kret = mach_vm_region (task, &address, &size, VM_REGION_BASIC_INFO,
             (vm_region_info_t) &info, &count, &object_name);

  if (kret != KERN_SUCCESS)
    {
      printf ("mach_vm_region: Error %d - %s", kret, mach_error_string(kret));
      return;
    }
  memcpy (&prev_info, &info, sizeof (vm_region_basic_info_data_t));
  prev_address = address;
  prev_size = size;
  nsubregions = 1;

  for (;;)
    {
      int print = 0;
      int done = 0;

      address = prev_address + prev_size;

      /* Check to see if address space has wrapped around. */
      if (address == 0)
    { 
        print = done = 1;
    }

      if (!done)
        {
          // Even on iOS, we use VM_REGION_BASIC_INFO_COUNT_64. This works.

          count = VM_REGION_BASIC_INFO_COUNT_64;


          kret =
            mach_vm_region (task, &address, &size, VM_REGION_BASIC_INFO,
                          (vm_region_info_t) &info, &count, &object_name);

          if (kret != KERN_SUCCESS)
            {
        /* iOS 6 workaround - attempt to reget the task port to avoiD */
        /* "(ipc/send) invalid destination port" (1000003 or something) */
        task_for_pid(mach_task_self(),g_pid, &task);

        kret =
            mach_vm_region (task, &address, &size, VM_REGION_BASIC_INFO,
                              (vm_region_info_t) &info, &count, &object_name);


        }
       if (kret != KERN_SUCCESS)
    {
        fprintf (stderr,"mach_vm_region failed for address %p - Error: %x\n", address,(kret));
              size = 0;
    if (address >= 0x4000000) return;
              print = done = 1;
            }
        }

      if (address != prev_address + prev_size)
        print = 1;

      if ((info.protection != prev_info.protection)
          || (info.max_protection != prev_info.max_protection)
          || (info.inheritance != prev_info.inheritance)
          || (info.shared != prev_info.reserved)
          || (info.reserved != prev_info.reserved))
        print = 1;

      if (print)
        {
      int   print_size;
      char *print_size_unit;
          if (num_printed == 0)

            printf ("Region ");
          else
            printf ("   ... ");

       findListOfBinaries(task, prev_address, prev_size);
      /* Quick hack to show size of segment, which GDB does not */
      print_size = prev_size;
      if (print_size > 1024) { print_size /= 1024; print_size_unit = "K"; }
      if (print_size > 1024) { print_size /= 1024; print_size_unit = "M"; }
      if (print_size > 1024) { print_size /= 1024; print_size_unit = "G"; }
      /* End Quick hack */
          printf (" %p-%p [%d%s](%s/%s; %s, %s, %s) %s",
                           (prev_address),
                           (prev_address + prev_size),
               print_size,
               print_size_unit,
                           protection_bits_to_rwx (prev_info.protection),
                           protection_bits_to_rwx (prev_info.max_protection),
                           unparse_inheritance (prev_info.inheritance),
                           prev_info.shared ? "shared" : "private",
                           prev_info.reserved ? "reserved" : "not-reserved",
               behavior_to_text (prev_info.behavior));

          if (nsubregions > 1)
            printf (" (%d sub-regions)", nsubregions);

          printf ("\n");

          prev_address = address;
          prev_size = size;
          memcpy (&prev_info, &info, sizeof (vm_region_basic_info_data_t));
          nsubregions = 1;

          num_printed++;
        }
      else
        {
          prev_size += size;
          nsubregions++;
        }

      if ((max > 0) && (num_printed >= max))
    {
     printf ("Max %d num_printed %d\n", max, num_printed);
        done = 1;
    }

      if (done)
        break;
    }
}

void 
main(int argc, char **argv)
{

    struct vm_region_basic_info vmr;
    kern_return_t   rc;
    mach_port_t task;

    mach_vm_size_t  size = 8;
    vm_region_info_t    info = (vm_region_info_t) malloc(10000);
    mach_msg_type_number_t  info_count;
    mach_port_t     object_name;
    mach_vm_address_t   addr =1;
    int pid;

        if (!argv[1]) { printf ("Usage: %s <PID>\n"); exit (1);}
    pid = atoi(argv[1]);
    g_pid = pid; // req for iOS 6
    rc = task_for_pid(mach_task_self(),pid, &task);

    if (rc) { fprintf (stderr, "task_for_pid() failed with error %d - %s\n", rc, mach_error_string(rc)); exit(1); }
    printf ("RC %d - Task: %d\n",rc, task);


    macosx_debug_regions (task, addr, 1000);

    int i ;

    for ( i = 0; i < g_imageCount; i++)
    {
        printf("Image: %s loaded @%p\n",
            g_dii[i].imageFilePath, g_dii[i].imageLoadAddress);
    }
    printf("Done\n");


}

我还发现朱莉娅·埃文斯的 https://jvns.ca/blog/2018/01/26/mac-memory-maps/关于重新创建的博客文章vmmapRust 很有趣并且与你的问题相关。

您可以执行此操作的另一种方法是致电system with vmmap PID > file作为参数,然后从文件中读取进程映射信息system完成。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何在 C (MacOS) 中获取进程虚拟内存映射的信息 的相关文章

  • 调用 McAfee 病毒扫描引擎

    我收到客户的请求 要求使用他们服务器上的 McAfee 病毒扫描将病毒扫描集成到应用程序中 我做了一些调查 发现 McScan32 dll 是主要的扫描引擎 它导出各种看起来有用的函数 我还发现提到了 McAfee Scan Engine
  • 在 xaml 中编写嵌套类型时出现设计时错误

    我创建了一个用户控件 它接受枚举类型并将该枚举的值分配给该用户控件中的 ComboBox 控件 很简单 我在数据模板中使用此用户控件 当出现嵌套类型时 问题就来了 我使用这个符号来指定 EnumType x Type myNamespace
  • 类型中的属性名称必须是唯一的

    我正在使用 Entity Framework 5 并且有以下实体 public class User public Int32 Id get set public String Username get set public virtual
  • 机器Epsilon精度差异

    我正在尝试计算 C 中双精度数和浮点数的机器 epsilon 值 作为学校作业的一部分 我在 Windows 7 64 位中使用 Cygwin 代码如下 include
  • std::list 线程push_back、front、pop_front

    std list 线程安全吗 我假设不是这样 所以我添加了自己的同步机制 我认为我有正确的术语 但我仍然遇到问题 每个函数都由单独的线程调用 Thread1 不能等待 它必须尽可能快 std list
  • 随着时间的推移,添加到 List 变得非常慢

    我正在解析一个大约有 1000 行的 html 表 我从一个字符串中添加 10 个字符串 td 每行到一个list td
  • 为什么 GCC 不允许我创建“内联静态 std::stringstream”?

    我将直接前往 MCVE include
  • 如何连接重叠的圆圈?

    我想在视觉上连接两个重叠的圆圈 以便 becomes 我已经有部分圆的方法 但现在我需要知道每个圆的重叠角度有多大 但我不知道该怎么做 有人有主意吗 Phi ArcTan Sqrt 4 R 2 d 2 d HTH Edit 对于两个不同的半
  • 如何使从 C# 调用的 C(P/invoke)代码“线程安全”

    我有一些简单的 C 代码 它使用单个全局变量 显然这不是线程安全的 所以当我使用 P invoke 从 C 中的多个线程调用它时 事情就搞砸了 如何为每个线程单独导入此函数 或使其线程安全 我尝试声明变量 declspec thread 但
  • C++ 多行字符串原始文字[重复]

    这个问题在这里已经有答案了 我们可以像这样定义一个多行字符串 const char text1 part 1 part 2 part 3 part 4 const char text2 part 1 part 2 part 3 part 4
  • WPF 数据绑定到复合类模式?

    我是第一次尝试 WPF 并且正在努力解决如何将控件绑定到使用其他对象的组合构建的类 例如 如果我有一个由两个单独的类组成的类 Comp 为了清楚起见 请注意省略的各种元素 class One int first int second cla
  • 在 Unity 中实现 Fur with Shells 技术

    我正在尝试在 Unity 中实现皮毛贝壳技术 http developer download nvidia com SDK 10 5 direct3d Source Fur doc FurShellsAndFins pdf Fins 技术被
  • 使用 C# 中的 CsvHelper 将不同文化的 csv 解析为十进制

    C 中 CsvHelper 解析小数的问题 我创建了一个从 byte 而不是文件获取 csv 文件的类 并且它工作正常 public static List
  • 复制目录下所有文件

    如何将一个目录中的所有内容复制到另一个目录而不循环遍历每个文件 你不能 两者都不Directory http msdn microsoft com en us library system io directory aspx nor Dir
  • C 函数 time() 如何处理秒的小数部分?

    The time 函数将返回自 1970 年以来的秒数 我想知道它如何对返回的秒数进行舍入 例如 对于100 4s 它会返回100还是101 有明确的定义吗 ISO C标准没有说太多 它只说time 回报 该实现对当前日历时间的最佳近似 结
  • 编译时展开 for 循环内的模板参数?

    维基百科 here http en wikipedia org wiki Template metaprogramming Compile time code optimization 给出了 for 循环的编译时展开 我想知道我们是否可以
  • C++ 中的参考文献

    我偶尔会在 StackOverflow 上看到代码 询问一些涉及函数的重载歧义 例如 void foo int param 我的问题是 为什么会出现这种情况 或者更确切地说 你什么时候会有 对参考的参考 这与普通的旧参考有何不同 我从未在现
  • C# 使用“?” if else 语句设置值这叫什么

    嘿 我刚刚看到以下声明 return name null name NA 我只是想知道这在 NET 中叫什么 是吗 代表即然后执行此操作 这是一个俗称的 条件运算符 三元运算符 http en wikipedia org wiki Tern
  • 在OpenGL中,我可以在坐标(5, 5)处精确地绘制一个像素吗?

    我所说的 5 5 正是指第五行第五列 我发现使用屏幕坐标来绘制东西非常困难 OpenGL 中的所有坐标都是相对的 通常范围从 1 0 到 1 0 为什么阻止程序员使用屏幕坐标 窗口坐标如此严重 最简单的方法可能是通过以下方式设置投影以匹配渲
  • 如何确定 CultureInfo 实例是否支持拉丁字符

    是否可以确定是否CultureInfo http msdn microsoft com en us library system globalization cultureinfo aspx我正在使用的实例是否基于拉丁字符集 我相信你可以使

随机推荐

  • 将字符串转换为日期 [包含年份和季度]

    我有一个 pandas 数据框 其中一列包含年份和季度的字符串 格式如下 2015Q1 我的问题 如何将其转换为两个日期时间列 一个代表年份 一个代表季度 您可以使用split http pandas pydata org pandas d
  • EF Core 中修改的实体所拥有的类型属性不持久

    我正在尝试在 EF Core 中实现一些在 EF 6 中对我来说非常有效的目标 我正在序列化 a 的内容List
  • 不使用密钥库的客户端证书

    我试图弄清楚是否有任何方法可以让 NET 客户端使用客户端证书而不以任何方式涉及 Windows 密钥库 我有一个这样的代码片段 test1 Service s new test1 Service X509Certificate c X50
  • JFreeChart日期轴格式化问题

    我有一个时间序列图表 我的 X 轴是日期 Y 轴只是数字 我正在尝试格式化 x 轴上的日期 但是我不断收到异常 我的代码如下 TimeSeries trueSeries new TimeSeries True Data TimeSeries
  • firebase 正在从数据库中检索已删除的数据

    我正在使用 firebase 实时数据库 它对于我的应用程序的某些部分运行良好 我正在阅读 youtube 上的一个教程 该教程用用户填充 collectionView 它使用 NSDictionary 获取照片 URL 和用户名 并将它们
  • Spring Boot + Angular 文件上传无法上传同一文件两次

    chrome 网络下出错 timeStamp status 400 error Bad Request message Required request part file is not present path url as hosted
  • malloc 和 realloc 之间的区别?

    假设我有两个用于创建 10 个元素的整数数组的代码示例 int pi int 0 realloc pi 10 另一种是正常写法 即 int pi pi malloc 10 sizeof int 现在 我的问题是 第一种赋值是合法的 但没有被
  • 点间最短距离算法

    给定平面上的一组点 找到由这些点中的任意两个点形成的最短线段 我怎样才能做到这一点 最简单的方法显然是计算每个距离 但我需要另一种算法来比较 http en wikipedia org wiki Closest pair of points
  • Nginx 身份验证(本地网络除外)

    来自 apache2 的一个功能我无法实现 仅要求对外部访问进行身份验证 但对本地网络上的用户进行免费访问 有什么想法可以轻松处理这种情况吗 任何帮助 将不胜感激 我已经删除了之前的答案 并想建议我在下面提供的解决方案 我做了一些搜索 找到
  • .net标准类库-不支持分布式事务错误

    我在 net标准类库项目中开发了一个日志服务 它可以完美工作 无需系统 事务 事务范围类 当我向进程添加事务时 记录器插入方法引发异常 该平台不支持分布式事务 当我使用 System Transaction 将代码添加到核心控制台应用程序时
  • 我应该如何在KAA中实现Rest API

    我使用 Kaa 沙箱发送通知 我必须在窗口终端上运行这些代码 curl v S u devuser devuser123 F notification applicationId 3 schemaId 4 topicId 1 type US
  • Python 全局变量的疯狂

    您有三个文件 main py second py 和 common py 通用 py usr bin python GLOBAL ONE Frank main py usr bin python from common import fro
  • Blazor - 在运行时更改 UI CultureInfo

    我需要根据每种文化的资源文件在运行时更改文化 Expected 用户单击从当前语言切换到另一种语言的按钮 页面中的文本根据每种文化的资源文件进行刷新 Actual 用户单击从当前语言切换到另一种语言的按钮 页面中的文本根据每种文化的资源文件
  • Visual Studio 2015 中没有针对 AngularJS TypeScript 的 IntelliSense

    我正在 Visual Studio 2015 的 TypeScript 中使用 ASP NET 5 和 AngularJS 构建一个应用程序 但是 即使在通过 NuGet 安装了 AngularJS Core 和 DefinelyTyped
  • React库如何直接需要它的源库?

    我正在查看 Facebook 的 React 源代码 发现在整个项目源代码中 他们没有指定加载自己模块的相对路径 例如 而不是做var foo require bar foobar 图书馆使用var foo require foobar 就
  • 批量插入到自引用表中

    这个问题是一部分一个更复杂的问题 https stackoverflow com questions 55047552 how to lock and unlock a sql server table noredirect 1 comme
  • 重命名 MySQL 中的外键列

    我们正在尝试重命名 MySQL 5 1 31 InnoDB 中作为另一个表的外键的列 起初 我们尝试使用 Django South 但遇到了一个已知问题 http south aeracode org ticket 243 http sou
  • 适用于运行 iOS 的 Cocoa iPhone 和 iPad 的 WebDAV 服务器

    我正在寻找适用于 iPhone 和 iPad 的 WebDAV 服务器 最好的开源服务器 以便集成到我的应用程序中 应该可以列出 下载和上传文件 我想将服务器根目录指向我的应用程序的 文档 文件夹 因此如果可能 这些方法应直接映射到文件系统
  • 使用 Gitolite 自动部署 PHP 脚本

    我想在本地测试服务器上设置自动部署脚本 我在 ubuntu 桌面 11 04 上使用 gitolite 我使用 apt get install gitolite 安装 gitolite 因此它创建用户 gitolite 和组 gitolit
  • 如何在 C (MacOS) 中获取进程虚拟内存映射的信息

    我想要获取有关 C MacOS 中当前进程的进程虚拟内存布局 堆栈 libc 堆和代码 的信息 我读到在 Linux 中你可以通过查看来做到这一点 proc