一:背景

1. 讲故事

在高级调试的旅行中,发现有不少人对符号表不是很清楚,其实简而言之符号表中记录着一些程序的生物特征,比如哪个地址是函数(签名信息),哪个地址是全局变量,静态变量,行号是多少,数据类型是什么 等等,目的就是辅助我们可视化的调试,如果没有这些辅助我们看到的都是一些无意义的汇编代码,逆向起来会非常困难,这一篇我们就来系统的聊一聊。

二:程序编译的四个阶段

1. 案例代码

要想理解符号表,首先需要理解 代码文件 是如何变成 可执行文件 的,即如下的四个阶段。

  • 预处理阶段
  • 编译阶段
  • 汇编阶段
  • 链接阶段

为了能够看到每一个阶段,用 gcc 的相关命令手工推进,并用 chatgpt 写一段测试代码,包含全局变量,静态变量,函数等信息。


#include <stdio.h>
#define PI 3.1415926 int global_var = 10; void func() { static int static_var = 5; printf("global_var = %d, static_var = %d PI=%f\n", global_var, static_var,PI); global_var++;
static_var++;
} int main() {
func();
func(); return 0;
}

接下来用 gcc --help 命令查看下需要使用的命令列表。


[root@localhost data]# gcc --help
Usage: gcc [options] file...
Options:
-E Preprocess only; do not compile, assemble or link
-S Compile only; do not assemble or link
-c Compile and assemble, but do not link
-o <file> Place the output into <file>
...

2. 预编译阶段

预处理主要做的就是代码整合,比如将 #include 文件导入,将 #define 宏替换等等,接下来使用 gcc -E 进行预处理。


[root@localhost data]# gcc main.c -E -o main.i
[root@localhost data]# ls
main.c main.i

可以看到这个 main.c 文件已经膨胀到了 858 行了。

3. 编译阶段

前面阶段是把代码预处理好,接下来就是将C代码编译成汇编代码了,使用 gcc -S 即可。


[root@localhost data]# gcc main.c -S -o main.s -masm=intel
[root@localhost data]# ls
main.c main.i main.s

从图中可以看到汇编代码中也有很多辅助信息,比如 global_var 是一个 @object 变量,类型为 int,在 .rodata 只读数据段中,目的就是给汇编阶段打辅助。

4. 汇编阶段

有了汇编代码之后,接下来就是将 汇编代码 转成 机器代码,这个阶段会产生二进制文件,并且会构建 section 信息以及符号表信息,可以使用 gcc -c 即可。


[root@localhost data]# gcc main.c -c -o main.o -masm=intel
[root@localhost data]# ls
main.c main.i main.o main.s

二进制文件模式默认是不能可视化打开的,可以借助于 objdump 工具。


[root@localhost data]# objdump
-h, --[section-]headers Display the contents of the section headers
-t, --syms Display the contents of the symbol table(s) [root@localhost data]# objdump -t main.o main.o: file format elf64-x86-64 SYMBOL TABLE:
0000000000000000 l df *ABS* 0000000000000000 main.c
0000000000000000 l d .text 0000000000000000 .text
0000000000000000 l d .data 0000000000000000 .data
0000000000000000 l d .bss 0000000000000000 .bss
0000000000000000 l d .rodata 0000000000000000 .rodata
0000000000000004 l O .data 0000000000000004 static_var.2179
0000000000000000 l d .note.GNU-stack 0000000000000000 .note.GNU-stack
0000000000000000 l d .eh_frame 0000000000000000 .eh_frame
0000000000000000 l d .comment 0000000000000000 .comment
0000000000000000 g O .data 0000000000000004 global_var
0000000000000000 g F .text 0000000000000058 func
0000000000000000 *UND* 0000000000000000 printf
0000000000000058 g F .text 000000000000001f main

在上面的符号表中看到了 func函数以及 static_varglobal_var 以及所属的 section。

5. 链接阶段

这个阶段主要是将多个二进制代码文件进一步整合变成可在操作系统上运行的可执行文件,可以使用 gcc -o


[root@localhost data]# gcc main.c -o main
[root@localhost data]# ls
main main.c main.i main.o main.s
[root@localhost data]# ./main
global_var = 10, static_var = 5 PI=3.141593
global_var = 11, static_var = 6 PI=3.141593
[root@localhost data]# objdump -t main main: file format elf64-x86-64 SYMBOL TABLE:
...
0000000000601034 g O .data 0000000000000004 global_var
0000000000601034 g O .data 0000000000000004 global_var
...
000000000040052d g F .text 0000000000000058 func
...

相比汇编阶段,这个阶段的 符号表 中的第一列都是有地址值的,是相对模块的偏移值,比如说: module+0x000000000040052d 标记的是 func 函数。

上面是 linux 上的可执行文件的符号表信息,有些朋友说我是 windows 平台上的,怎么看符号表信息呢?

三:Windows 上的 pdb 解析

1. 观察 pdb 文件

上一节我们看到的是 linux 上 elf格式 的可执行文件,这一节看下 windows 平台上的PE文件 的符号表信息是什么样的呢?有了前面四阶段编译的理论基础,再聊就比较简单了。

在 windows 平台上 符号表信息 是藏在 pdb 文件中的,这种拆开的方式是有很大好处的,如果需要调试代码,windbg 会自动加载 pdb 文件,无调试的情况下就不需要加载 pdb 了,减少了可执行文件的大小,也提升了性能。

接下来用 SymView.exe 这种工具去打开 pdb 文件,截图如下:

从图中可以看到,符号表信息高达 10968 个,并且 func 函数的入口地址是在 module +0x11870 处,相当于做了一个标记,接下来我们拿这个func做一个测试。

2. 有 pdb 的 func 函数

首先说一下为什么通过 exe 可以找到 pdb,这是因为 PE 头的 DIRECTORY_ENTRY_DEBUG 节中记录了 pdb 的地址。

只要这个路径有 pdb 就可以在 windbg 运行中按需加载了,然后通过 u MySample.exe+0x11870 观察,截图如下:

图中显示的非常清楚,地址 00fd1870 就是 func 的入口地址,让一个无意义的地址马上有意义起来了,哈哈~~~

3. 无 pdb 的 func 函数

这一小节是提供给好奇的朋友的,如果没有 pdb,那汇编上又是一个什么模样,为了找到 func 的入口地址,我们内嵌一个 int 3 ,然后把 pdb 给删掉,代码如下:


int main() { __asm {
int 3;
}
func();
func(); return 0;
}

从图中可以看到,func 标记已经没有了,取而代之的都是 module+0xxx,这就会给我们逆向调试带来巨大的障碍。

三: 总结

总而言之,符号表就是对茫茫内存进行标记,就像百度地图一样,让我们知道某个经纬度上有什么建筑,让无情的地理坐标更加有温度,让世界更美好。

聊一聊 .NET高级调试 中必知的符号表的更多相关文章

  1. 浏览器调试的必知必会,零基础足够详细-第一节console面板、移动端调试

    前言 本文已经发布视频点击查看 开发过程中,浏览器的调试非常重要,可以说是必备的技巧,本文我就会分享一些自己掌握的技巧,欢迎补充 我们默认使用Chrome浏览器,但是你使用新edge浏览器也是可以的 ...

  2. 高级程序员必知必会,一文详解MySQL主从同步原理,推荐收藏

    1. MySQL主从同步实现方式 MySQL主从同步是基于Bin Log实现的,而Bin Log记录的是原始SQL语句. Bin Log共有三种日志格式,可以binlog_format配置参数指定. ...

  3. Mach-O在内存中符号表地址、字符串表地址的计算

    KSCrash 是一个用于 iOS 平台的崩溃捕捉框架,最近读了其部分源码,在 KSDynamicLinker 文件中有一个函数,代码如下: /** Get the segment base addr ...

  4. .NET程序员项目开发必知必会—Dev环境中的集成测试用例执行时上下文环境检查(实战)

    Microsoft.NET 解决方案,项目开发必知必会. 从这篇文章开始我将分享一系列我认为在实际工作中很有必要的一些.NET项目开发的核心技术点,所以我称为必知必会.尽管这一系列是使用.NET/C# ...

  5. Visual Studio 使用及调试必知必会

    原文:Visual Studio 使用及调试必知必会   一:C# CODING 技巧 1:TODO 然后 CTRL + W + T,打开任务列表,选中 Comments,就会显示所有待做的任务 2: ...

  6. input屏蔽历史记录 ;function($,undefined) 前面的分号是什么用处 JSON 和 JSONP 两兄弟 document.body.scrollTop与document.documentElement.scrollTop兼容 URL中的# 网站性能优化 前端必知的ajax 简单理解同步与异步 那些年,我们被耍过的bug——has

    input屏蔽历史记录   设置input的扩展属性autocomplete 为off即可 ;function($,undefined) 前面的分号是什么用处   ;(function($){$.ex ...

  7. 第4节:Java基础 - 必知必会(中)

    第4节:Java基础 - 必知必会(中) 本小节是Java基础篇章的第二小节,主要讲述抽象类与接口的区别,注解以及反射等知识点. 一.抽象类和接口有什么区别 抽象类和接口的主要区别可以总结如下: 抽象 ...

  8. SQL 必知必会·笔记<11>创建高级联结

    1. 使用表别名 SQL 除了可以对列名和计算字段使用别名,还允许给表名起别名.这样 做有两个主要理由: 缩短SQL 语句: 允许在一条SELECT 语句中多次使用相同的表. 使用表别名示例: SEL ...

  9. mysql必知必会-创建高级联结

    使用表别名 使用别名引用被检索的表列 别名除了用于列名和计算字段外,SQL还允许给表名起别名.这样做 有两个主要理由: 缩短SQL语句: 允许在单条 SELECT 语句中多次使用相同的表. 可以看到, ...

  10. SQL 必知必会

    本文介绍基本的 SQL 语句,包括查询.过滤.排序.分组.联结.视图.插入数据.创建操纵表等.入门系列,不足颇多,望诸君指点. 注意本文某些例子只能在特定的DBMS中实现(有的已标明,有的未标明),不 ...

随机推荐

  1. MySQL-通过存储过程来添加和删除分区(List分区)

    1.背景原因 当前MySQL不支持在添加和删除分区时,使用IF NOT EXISTS和IF EXISTS.所以在执行调度任务时,直接通过ADD PARTITION和DROP PARTITION不可避免 ...

  2. Oracle:Ora-01652无法通过128(在temp表空间中)扩展temp段的过程-解决步骤

    现象:查询select * from v$sql时提示"Ora-01652无法通过128(在temp表空间中)扩展temp段的过程" 临时文件是不存储的,可以将数据库重启,重启后重 ...

  3. ERROR: nginx-1.22.1 installation failed.

    libraries. You can either do not enable the module or install the libraries.make: *** No rule to mak ...

  4. Thinking in Java 4th Edition Source Code

    Thinking in Java 4th Edition Source Code Instructions for downloading, installing and testing the so ...

  5. 几句话了解元数据(Metadata)

    元数据就是关于数据的数据(data about data),主要是描述数据属性(property)的信息,用来支持如指示存储位置.历史数据.资源查找.文件记录等功能. 要理解元数据,首先要知道&quo ...

  6. Travelling Salesman and Special Numbers

    prologue 模拟赛的一道题,结果没做出来,丢大人,败大兴.所以过来糊一篇题解. analysis 我们看到数据范围这么大,那么肯定不可以一个一个遍历(废话),所以就要考虑这个题目的性质. 我们先 ...

  7. 2020/4/27 日常补坑-tarjan第一道awa

    第一题 luoguP1407 我们已知n对夫妻的婚姻状况,称第i对夫妻的男方为Bi,女方为Gi.若某男Bi与某女Gj曾经交往过(无论是大学,高中,亦或是幼儿园阶段,i≠j),则当某方与其配偶(即Bi与 ...

  8. C++ STL标准容器的特点和典型的使用场景

    概念和作用 C++标准模板库(Standard Template Library,STL)提供了一组通用的模板类和函数,用于处理常见的数据结构和算法.STL中的标准容器是其中的重要组成部分,它们提供了 ...

  9. 比赛总结:Japan Registry Services (JPRS) Programming Contest 2023 (AtCoder Beginner Contest 324)

    比赛:Japan Registry Services (JPRS) Programming Contest 2023 (AtCoder Beginner Contest 324) A-same 1.常 ...

  10. Memcached的基本操作

    一.Memcache使用场景1.非持久化存储:对数据存储要求不高2.分布式存储:不适合单机使用3.key/value存储:格式简单,不支持list,array数据格式二.系统类$m=new Memca ...