笔记¶
Benchmark : GUPS
Introduction
GUPS (Giga十亿 UPdates per Second) is a measurement that profiles the memory architecture of a system and is a measure of performance similar to MFLOPS.
The HPCS HPCchallenge RandomAccess benchmark is intended to exercise the GUPS capability of a system, much like the LINPACK benchmark is intended to exercise the MFLOPS capability of a computer. In each case, we would expect these benchmarks to achieve close to the "peak" capability of the memory system. The extent of the similarities between RandomAccess and LINPACK are limited to both benchmarks attempting to calculate a peak system capability.
definition of GUPS
GUPS is calculated by identifying the number of memory locations that can be randomly updated in one second, divided by 1 billion (1e9).
- The term "randomly" means that there is little relationship between one address to be updated and the next, except that they occur in the space of one half the total system memory. (只用一半内存?)
- An update is a read-modify-write operation on a table of 64-bit words. An address is generated, the value at that address read from memory, modified by an integer operation (add, and, or, xor) with a literal value, and that new value is written back to memory.
Extensibility
- We are interested in knowing the GUPS performance of both entire systems and system subcomponents --- e.g., the GUPS rating of a distributed memory multiprocessor the GUPS rating of an SMP node, and the GUPS rating of a single processor.
- While there is typically a scaling of FLOPS with processor count, a similar phenomenon may not always occur for GUPS.
Principle
Select the memory size to be the power of two such that 2^n <= 1/2 of the total memory. Each CPU operates on its own address stream, and the single table may be distributed among nodes. The distribution of memory to nodes is left to the implementer. A uniform data distribution may help balance the workload, while non-uniform data distributions may simplify the calculations that identify processor location by eliminating the requirement for integer divides. A small (less than 1%) percentage of missed updates are permitted.
Installation
Download
official web or from GitHub
Usage
需要进一步的研究学习
暂无
遇到的问题
暂无
开题缘由、总结、反思、吐槽~~
参考文献
上面回答部分来自ChatGPT-3.5,没有进行正确性的交叉校验。
无
Echart
echart
快速上手
- 在github仓库dist目录下拷贝echart.js 和 echart.min.js到index.html目录下。
Vue-ECharts
参考中文文档
实践
简单柱状图
option = {
title: {
text: 'Percentage of page walk time overhead caused by data TLB misses',
subtext: 'GUPS RandomAccess benchmark'
},
tooltip: {
trigger: 'axis',
axisPointer: {
type: 'shadow'
}
},
xAxis: {
name: 'input-data \nsize',
type: 'category',
data: ['2^29', '2^30', '2^31', '2^32'],
},
yAxis: {
// name: 'Percentage',
type: 'value',
title: 'align'
},
series: [
{
name: "Percentage",
data: [13.20, 19.50, 45.22,66.81],
type: 'bar',
label: {
show: true,
position: 'top'
},
}
]
};
需要进一步的研究学习
暂无
遇到的问题
暂无
开题缘由、总结、反思、吐槽~~
参考文献
无
Disassembly file: objdump
objdump file
Disassembly of section .plt:
0000000000402020 <.plt>:
402020: ff 35 e2 bf 02 00 pushq 0x2bfe2(%rip) # 42e008 <_GLOBAL_OFFSET_TABLE_+0x8>
402026: ff 25 e4 bf 02 00 jmpq *0x2bfe4(%rip) # 42e010 <_GLOBAL_OFFSET_TABLE_+0x10>
40202c: 0f 1f 40 00 nopl 0x0(%rax)
0000000000402030 <_Znam@plt>:
402030: ff 25 e2 bf 02 00 jmpq *0x2bfe2(%rip) # 42e018 <_Znam@GLIBCXX_3.4>
402036: 68 00 00 00 00 pushq $0x0
40203b: e9 e0 ff ff ff jmpq 402020 <.plt>
0000000000402040 <_ZNSo3putEc@plt>:
402040: ff 25 da bf 02 00 jmpq *0x2bfda(%rip) # 42e020 <_ZNSo3putEc@GLIBCXX_3.4>
402046: 68 01 00 00 00 pushq $0x1
40204b: e9 d0 ff ff ff jmpq 402020 <.plt>
.plt节主要实现了使用过程链接表(Procedure Linkage Table)实现延迟绑定的功能。
openmp相关的执行
omp_outlined函数
问题:objdump 程序 有许多 类似 <.omp_outlined..16>: 的函数,但是main函数里并没有调用。实际openmp是怎么执行这些代码的呢?
在使用了OpenMP指令的C/C++程序编译后,编译器会自动生成一些名为.omp_outlined.的函数。这些函数是OpenMP所需要的运行时支持函数,不是直接在main函数中调用的,其执行方式主要有以下几种:
- 一些关键的运行时函数,如线程创建、同步等,是在程序启动时由OpenMP运行时库初始化和直接调用的。
- 对于并行region,编译器会在主线程发起并行时调用对应的
.omp_outlined.函数创建线程并发布工作。 - 一些工作分配,同步等调用也是隐式通过运行时库的支持函数实现的。
- 对于并行循环,编译器会将循环体移动到
.omp_outlined.函数,在循环分配工作时调用。 - 减少指令,作用域变化等也会引发这些函数的调用。
所以.omp_outlined.函数的执行是隐式通过运行时库触发和调度的,不需要用户代码直接调用。它们是OpenMP实现所必须的,由编译器和运行时库协调完成。用户只需要编写OpenMP指令,不必关心具体的调用细节。
总体来说,这是一种让并行执行透明化的实现机制,减少了用户的工作量。
OpenMP的汇编代码
不同平台不同,有GOMP_parallel_start开头的。也有如下x86平台的
405854: 48 c7 84 24 a0 00 00 movq $0x4293b9,0xa0(%rsp)
40585b: 00 b9 93 42 00
405860: 48 8d bc 24 90 00 00 lea 0x90(%rsp),%rdi
405867: 00
405868: ba 10 5f 40 00 mov $0x405f10,%edx
40586d: be 02 00 00 00 mov $0x2,%esi
405872: 4c 89 f9 mov %r15,%rcx
405875: 4c 8b 44 24 20 mov 0x20(%rsp),%r8
40587a: 31 c0 xor %eax,%eax
40587c: e8 ff cb ff ff callq 402480 <__kmpc_fork_call@plt>
405881: 48 8b 7c 24 60 mov 0x60(%rsp),%rdi
这段汇编代码实现了OpenMP中的并行构造,主要执行了以下几个步骤:
- 在栈上写入一个常量0x4293b9,可能是team的参数 (48 c7 84 24)
- 准备参数,获取rsp+0x90地址到rdi作为第1参数 (%rdi)
- 设置edx为0x405f10,可能是kmp_routine函数地址
- esi设置为2,可能表示有2个参数
- r15设置到rcx,传入线程号参数
- r8传入栈上第0x20个参数,可能是void* shareds参数
- 清空eax,一些调用约定使用
- 调用
__kmpc_fork_call函数,这是OpenMP的runtime库函数,用来并行执行一个函数 kmpcfork multiple parallel call?- 最后将返回值保存在rdi指定的栈空间上
所以这段代码实现了调用OpenMP runtime并行执行一个函数的操作,准备参数,调用runtime API,获取返回值的一个流程。
利用runtime库的支持函数可以实现汇编级别的OpenMP并行性。
readelf
各section位置以及含义,参考文档
$ readelf -S bfs.inj
There are 37 section headers, starting at offset 0xbe8e8:
在文件内 0xbe8e8字节开始
Section Headers:
[Nr] Name Type Address Offset
Size EntSize Flags Link Info Align
序号 节名称 节类型 节的虚拟地址偏移量 节在文件中的偏移量
节大小 每个条目的大小(如果大小固定) 节的标志 节的链接信息 节的额外信息 节的信息对齐方式
[ 0] NULL 0000000000000000 00000000
0000000000000000 0000000000000000 0 0 0
[ 1] .interp PROGBITS 00000000004002a8 000002a8
000000000000001c 0000000000000000 A 0 0 1
[ 2] .note.gnu.build-i NOTE 00000000004002c4 000002c4
0000000000000024 0000000000000000 A 0 0 4
[ 3] .note.ABI-tag NOTE 00000000004002e8 000002e8
0000000000000020 0000000000000000 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0000000000400308 00000308
000000000000005c 0000000000000000 A 5 0 8
[ 5] .dynsym DYNSYM 0000000000400368 00000368
00000000000007e0 0000000000000018 A 6 1 8
[ 6] .dynstr STRTAB 0000000000400b48 00000b48
0000000000000b1d 0000000000000000 A 0 0 1
[ 7] .gnu.version VERSYM 0000000000401666 00001666
00000000000000a8 0000000000000002 A 5 0 2
[ 8] .gnu.version_r VERNEED 0000000000401710 00001710
0000000000000110 0000000000000000 A 6 5 8
[ 9] .rela.dyn RELA 0000000000401820 00001820
00000000000000f0 0000000000000018 A 5 0 8
[10] .rela.plt RELA 0000000000401910 00001910
00000000000006c0 0000000000000018 AI 5 24 8
[11] .init PROGBITS 0000000000402000 00002000
000000000000001b 0000000000000000 AX 0 0 4
[12] .plt PROGBITS 0000000000402020 00002020
0000000000000490 0000000000000010 AX 0 0 16
[13] .text PROGBITS 00000000004024b0 000024b0
0000000000026475 0000000000000000 AX 0 0 16
[14] .fini PROGBITS 0000000000428928 00028928
000000000000000d 0000000000000000 AX 0 0 4
[15] .rodata PROGBITS 0000000000429000 00029000
0000000000001180 0000000000000000 A 0 0 16
[16] .eh_frame_hdr PROGBITS 000000000042a180 0002a180
00000000000002ac 0000000000000000 A 0 0 4
[17] .eh_frame PROGBITS 000000000042a430 0002a430
0000000000001780 0000000000000000 A 0 0 8
[18] .gcc_except_table PROGBITS 000000000042bbb0 0002bbb0
00000000000005d0 0000000000000000 A 0 0 4
[19] .init_array INIT_ARRAY 000000000042dbc8 0002cbc8
0000000000000010 0000000000000008 WA 0 0 8
[20] .fini_array FINI_ARRAY 000000000042dbd8 0002cbd8
0000000000000008 0000000000000008 WA 0 0 8
[21] .data.rel.ro PROGBITS 000000000042dbe0 0002cbe0
00000000000001f0 0000000000000000 WA 0 0 8
[22] .dynamic DYNAMIC 000000000042ddd0 0002cdd0
0000000000000220 0000000000000010 WA 6 0 8
[23] .got PROGBITS 000000000042dff0 0002cff0
0000000000000010 0000000000000008 WA 0 0 8
[24] .got.plt PROGBITS 000000000042e000 0002d000
0000000000000258 0000000000000008 WA 0 0 8
[25] .data PROGBITS 000000000042e258 0002d258
0000000000000010 0000000000000000 WA 0 0 8
[26] .bss NOBITS 000000000042e280 0002d268
0000000000000180 0000000000000000 WA 0 0 64
[27] .comment PROGBITS 0000000000000000 0002d268
000000000000004a 0000000000000001 MS 0 0 1
[28] .debug_info PROGBITS 0000000000000000 0002d2b2
000000000002a06e 0000000000000000 0 0 1
[29] .debug_abbrev PROGBITS 0000000000000000 00057320
0000000000000a57 0000000000000000 0 0 1
[30] .debug_line PROGBITS 0000000000000000 00057d77
000000000000af9a 0000000000000000 0 0 1
[31] .debug_str PROGBITS 0000000000000000 00062d11
0000000000010328 0000000000000001 MS 0 0 1
[32] .debug_loc PROGBITS 0000000000000000 00073039
0000000000042846 0000000000000000 0 0 1
[33] .debug_ranges PROGBITS 0000000000000000 000b587f
00000000000054c0 0000000000000000 0 0 1
[34] .symtab SYMTAB 0000000000000000 000bad40
00000000000018c0 0000000000000018 35 106 8
[35] .strtab STRTAB 0000000000000000 000bc600
0000000000002177 0000000000000000 0 0 1
[36] .shstrtab STRTAB 0000000000000000 000be777
000000000000016c 0000000000000000 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)
字段含义
- Type 字段,具体含义参考文档1-10
- Link 字段中的值是节头表中节头条目的索引,索引从0开始,表示第一个节头表条目,依此类推。比如5 代表与
[ 5] .dynsym有关
值得注意
One section type, SHT_NOBITS described below, occupies no
space in the file, and its sh_offset member locates the conceptual placement in the
file.
so the number "2d258" remains unchanged.
[25] .data PROGBITS 000000000042e258 0002d258
0000000000000010 0000000000000000 WA 0 0 8
[26] .bss NOBITS 000000000042e280 0002d268
0000000000000180 0000000000000000 WA 0 0 64
.got
global offset table
.plt
This section holds the procedure linkage table. See ‘‘Special Sections’’ in Part 1 and ‘‘Procedure Linkage Table’’ in Part 2 for more information.
Function symbols (those with type STT_FUNC) in shared object files have special significance. When another object file references a function from a shared object, the link editor automatically creates a procedure linkage table entry for the referenced symbol.
需要进一步的研究学习
暂无
遇到的问题
暂无
开题缘由、总结、反思、吐槽~~
参考文献
上面回答部分来自ChatGPT-3.5,没有进行正确性的交叉校验。
无
Linux Executable file: Structure & Running
可执行文件历史溯源

- COFF是32位System V平台上使用的一种格式。
- 它允许使用共享库和调试信息。
- 然而,它在节的最大数量和节名称的长度限制方面存在缺陷。
- 它也不能提供C++等语言的符号调试信息。
- 然而,像XCOFF(AIX)和ECOFF(DEC,SGI)这样的扩展克服了这些弱点,并且有一些版本的Unix使用这些格式。
- Windows的PE+格式也是基于COFF的。 可见可执行文件在不同平台上的规则还是有所不同的,后续会以UNIX ELF来分析
ELF 可执行目标文件

可执行目标文件的格式类似于可重定位目标文件的格式。
- ELF 头描述文件的总体格式。它还包括程序的入口点(entry point),也就是当程序运行时要执行的第一条指令的地址。
.text、.rodata和.data节与可重定位目标文件中的节是相似的,除了这些节已经被重定位到它们最终的运行时内存地址以外。.init节定义了一个小函数,叫做 _init,程序的初始化代码会调用它。- 因为可执行文件是完全链接的(已被重定位),所以它不再需要
.rel节。
可重定位目标文件
- 下面内容来自 深入理解计算机系统(CSAPP)的
7.4 可重定位目标文件一节 - 图 7-3 展示了一个典型的 ELF 可重定位目标文件的格式。

- ELF 头(Executable Linkable Format header)

- 以一个 16 字节的序列开始,这个序列描述了生成该文件的系统的字的大小和字节顺序。
- ELF 头剩下的部分包含帮助链接器语法分析和解释目标文件的信息。
- 其中包括 ELF 头的大小、目标文件的类型(如可重定位、可执行或者共享的)、机器类型(如 X86-64)、节头部表(section header table)的文件偏移,以及节头部表中条目的大小和数量。
- 节头部表描述不同节的位置和大小,其中目标文件中每个节都有一个固定大小的条目(entry)。
夹在 ELF 头和节头部表之间的都是节。一个典型的 ELF 可重定位目标文件包含下面几个节:
- .text:已编译程序的机器代码。
- 通常代码区是可共享的(即另外的执行程序可以调用它),使其可共享的目的是对于频繁被执行的程序,只需要在内存中有一份代码即可。
- 代码区通常是只读的,使其只读的原因是防止程序意外地修改了它的指令。
- .rodata:只读数据,比如 printf 语句中的格式串和开关语句的跳转表。
- .data:已初始化的全局和静态 C 变量。
- 已经初始化的全局变量、已经初始化?的静态变量(包括全局静态变量和局部静态变量)和常量数据(如字符串常量)。
- 局部 C 变量在运行时被保存在栈中,既不岀现在
.data节中,也不岀现在.bss节中。
- .bss:未初始化的全局和静态 C 变量,以及所有被初始化为 0 的全局或静态变量。
- 在目标文件中这个节不占据实际的空间,它仅仅是一个占位符。
- 目标文件格式区分已初始化和未初始化变量是为了空间效率:在目标文件中,未初始化变量不需要占据任何实际的磁盘空间。运行时,在内存中分配这些变量,初始值为 0。
- 用术语
.bss来表示未初始化的数据是很普遍的。它起始于 IBM 704 汇编语言(大约在 1957 年)中“块存储开始(Block Storage Start)”指令的首字母缩写,并沿用至今。 - 区分
.data和.bss节的简单方法是把 “bss” 看成是“更好地节省空间(Better Save Space)” 的缩写。
- .symtab:一个符号表,它存放在程序中定义和引用的函数和全局变量的信息。
- 一些程序员错误地认为必须通过 -g 选项来编译一个程序,才能得到符号表信息。实际上,每个可重定位目标文件在
.symtab中都有一张符号表(除非程序员特意用 STRIP 命令去掉它)。 - 然而,和编译器中的符号表不同,
.symtab符号表不包含局部变量的条目。
- 一些程序员错误地认为必须通过 -g 选项来编译一个程序,才能得到符号表信息。实际上,每个可重定位目标文件在
- .rel.text:一个 .text 节中位置的列表,当链接器把这个目标文件和其他文件组合时,需要修改这些位置。
- 一般而言,任何调用外部函数或者引用全局变量的指令都需要修改。另一方面,调用本地函数的指令则不需要修改。
- 注意,可执行目标文件中并不需要重定位信息,因此通常省略,除非用户显式地指示链接器包含这些信息。
- .rel.data:被模块引用或定义的所有全局变量的重定位信息。
- 一般而言,任何已初始化的全局变量,如果它的初始值是一个全局变量地址或者外部定义函数的地址,都需要被修改。
- .debug:一个调试符号表,其条目是
- 程序中定义的局部变量和类型定义,
- 程序中定义和引用的全局变量,
- 以及原始的 C 源文件。
- 只有以
-g选项调用编译器驱动程序时,才会得到这张表。
- .line:原始 C 源程序中的行号和
.text节中机器指令之间的映射。- 只有以
-g选项调用编译器驱动程序时,才会得到这张表。
- 只有以
- .strtab:一个字符串表,其内容包括
.symtab和.debug节中的符号表,以及节头部中的节名字。字符串表就是以 null 结尾的字符串的序列。
符号和符号表
每个可重定位目标模块 m 都有一个符号表.symtab,它包含 m 定义和引用的符号的信息。在链接器的上下文中,有三种不同的符号:
- (出)由模块 m 定义并能被其他模块引用的全局符号。
- 全局链接器符号对应于非静态的 C 函数和全局变量。
- (入)由其他模块定义并被模块 m 引用的全局符号。
- 这些符号称为外部符号,对应于在其他模块中定义的非静态 C 函数和全局变量。
- 只被模块 m 定义和引用的局部符号。
-
对应于带 static 属性的 C 函数和全局变量。这些符号在模块 m 中任何位置都可见,但是不能被其他模块引用。
-
本地链接器符号和本地程序变量的不同是很重要的。
.symtab中的符号表不包含对应于本地非静态程序变量的任何符号。- 这些符号在运行时在栈中被管理,链接器对此类符号不感兴趣。
- 有趣的是,定义为带有 C
static属性的本地过程变量是不在栈中管理的。 - 相反,编译器在 .data 或 .bss 中为每个定义分配空间,并在符号表中创建一个有唯一名字的本地链接器符号。
实践:readelf
使用命令readelf -s simple.o 可以读取符号表的内容。
示例程序的可重定位目标文件 main.o 的符号表中的最后三个条目。

- 开始的 8 个条目没有显示出来,它们是链接器内部使用的局部符号。
- 全局符号 main 定义的条目,
- 它是一个位于 .text 节
- 偏移量为 0(即 value 值)处的 24 字节函数。
- 其后跟随着的是全局符号 array 的定义
- 位于 .data 节
- 偏移量为 0 处的 8 字节目标。
-
外部符号 sum 的引用。
-
type 通常要么是数据,要么是函数。
- 符号表还可以包含各个节的条目,以及对应原始源文件的路径名的条目。
- binding 字段表示符号是本地的还是全局的。
- Ndx=1 表示 .text 节
- Ndx=3 表示 .data 节。
- ABS 代表不该被重定位的符号;
- UNDEF 代表未定义的符号,也就是在本目标模块中引用,但是却在其他地方定义的符号;
实践: 查看exe信息相关命令

进一步思考
- 小结:开辟局部变量、全局变量、malloc空间会影响可执行文件大小吗?对应汇编如何?存放的位置?运行时如何?
- 设计一个代码量小但是占空间很大的可执行文件。
- 因为已经初始化的全局变量、已经初始化的静态变量(包括全局静态变量和局部静态变量)会存储在data段,所以这些变量的大小会影响可执行文件的大小。

- static 与 const效果一样。
- 设计一个代码量小但是运行时占内存空间很大的可执行文件。
- malloc的空间会影响运行时的内存空间,但是不会影响可执行文件的大小。
- 将exe各节内容可视化解释(虽然现在是二进制)
-
编译的时候,头文件是怎么处理的?
-
data 与 bbs在存储时怎么区分全局与静态变量
- 符号表为什么有全局变量的符号,这些静态局部变量不需要吗?应该是需要的
- 请给出
.rel.text.rel.data的实例分析
线程与进程
- 调度:进程是资源管理的基本单位,线程是程序执行的基本单位。
- 切换:线程上下文切换比进程上下文切换要快得多。
- TLB是每个核私有的,如果一个核从一个进程切换到另一个进程,TLB要全部清空。
- 但是线程不需要,因为线程共享相同的虚拟地址空间。
- 所以线程切换开销远小于进程切换开销。
- 拥有资源: 进程是拥有资源的一个独立单位,线程不拥有系统资源,但是可以访问隶属于进程的资源。
- 系统开销: 创建或撤销进程时,系统都要为之分配或回收系统资源,如内存空间,I/O设备等,OS所付出的开销显著大于在创建或撤销线程时的开销,进程切换的开销也远大于线程切换的开销。
(软件)多线程与(CPU)超线程
线程和进程都可以用多核,但是线程共享进程内存(比如,openmp)
超线程注意也是为了提高核心的利用率,当有些轻量级的任务时(读写任务)核心占用很少,可以利用超线程把一个物理核心当作多个逻辑核心,一般是两个,来使用更多线程。AMD曾经尝试过4个。
单核多进程切换

进程结构
正在运行的程序,叫进程。每个进程都有完全属于自己的,独立的,不被干扰的内存空间。此空间,被分成几个段(Segment),分别是Text, Data, BSS, Heap, Stack。


esp ebp
push pop %ebp涉及到编译器调用函数的处理方式 application binary interface (ABI).- 如何保存和恢复寄存器
- 比如:cdecl(代表 C 声明)是 C 编程语言的调用约定,被许多 C 编译器用于 x86 体系结构。 在 cdecl 中,子例程参数在堆栈上传递。整数值和内存地址在
EAX寄存器中返回,浮点值在ST0x87寄存器中返回。寄存器EAX、ECX和EDX由调用方保存,其余寄存器由被叫方保存。x87浮点寄存器 调用新函数时,ST0到ST7必须为空(弹出或释放),退出函数时ST1到ST7必须为空。ST0在未用于返回值时也必须为空。


sp lp(Link Register) on ARM
0000822c <func>:
822c: e52db004 push {fp} ; (str fp, [sp, #-4]!) 如果嵌套调用 push {fp,lr}
8230: e28db000 add fp, sp, #0
8234: e24dd014 sub sp, sp, #20
8238: e50b0010 str r0, [fp, #-16]
823c: e3a03002 mov r3, #2
8240: e50b3008 str r3, [fp, #-8]
8244: e51b3008 ldr r3, [fp, #-8]
8248: e51b2010 ldr r2, [fp, #-16]
824c: e0030392 mul r3, r2, r3
8250: e1a00003 mov r0, r3
8254: e24bd000 sub sp, fp, #0
8258: e49db004 pop {fp} ; (ldr fp, [sp], #4) 如果嵌套调用 pop {fp,lr}
825c: e12fff1e bx lr ; MOV PC,LR
00008260 <main>:
8260: e92d4800 push {fp, lr}
8264: e28db004 add fp, sp, #4
8268: e24dd008 sub sp, sp, #8
826c: e3a03019 mov r3, #25
8270: e50b3008 str r3, [fp, #-8]
8274: e51b0008 ldr r0, [fp, #-8]
8278: ebffffeb bl 822c <func>
827c: e3a03000 mov r3, #0
8280: e1a00003 mov r0, r3
8284: e24bd004 sub sp, fp, #4
8288: e8bd8800 pop {fp, pc}
arm PC = x86 EIP ARM 为什么这么设计,就是为了返回地址不存栈,而是存在寄存器里。但是面对嵌套的时候,还是需要压栈。
栈区(stack)
由编译器自动分配释放,存放函数的参数值、返回值、局部变量等。在程序运行过程中实时加载和释放,因此,局部变量的生存周期为申请到释放该段栈空间。
WIndow系统一般是2MB。Linux可以查看ulimit -s ,通常是8M
栈空间最好保持在cache里,太大会存入内存。持续地重用栈空间有助于使活跃的栈内存保持在CPU缓存中,从而加速访问。进程中的每个线程都有属于自己的栈。向栈中不断压入数据时,若超出其容量就会耗尽栈对应的内存区域,从而触发一个页错误。
函数参数传递一般通过寄存器,太多了就存入栈内。
大数组seg fault
栈区(stack segment):由编译器自动分配释放,存放函数的参数的值,局部变量的值等。
局部变量空间是很小的,我们开一个a[1000000]就会导致栈溢出;而全局变量空间在Win 32bit 下可以达到4GB,因此不会溢出。
或者malloc使用堆的区域,但是记得free。
堆区(heap)
用于动态内存分配。堆在内存中位于BSS区和栈区之间。一般由程序员分配和释放,若程序员不释放,程序结束时有可能由OS回收。
分配的堆内存是经过字节对齐的空间,以适合原子操作。堆管理器通过链表管理每个申请的内存,由于堆申请和释放是无序的,最终会产生内存碎片。堆内存一般由应用程序分配释放,回收的内存可供重新使用。若程序员不释放,程序结束时操作系统可能会自动回收。
用户堆,每个进程有一个,进程中的每个线程都从这个堆申请内存,这个堆在用户空间。所谓内训耗光,一般就是这个用户堆申请不到内存了,申请不到分两种情况,一种是你 malloc 的比剩余的总数还大,这个是肯定不会给你了。第二种是剩余的还有,但是都不连续,最大的一块都没有你 malloc 的大,也不会给你。解决办法,直接申请一块儿大内存,自己管理。
除非特殊设计,一般你申请的内存首地址都是偶地址,也就是说你向堆申请一个字节,堆也会给你至少4个字节或者8个字节。
堆有一个堆指针(break brk),也是按照栈的方式运行的。内存映射段是存在在break brk指针与esp指针之间的一段空间。
在Linux中当动态分配内存大于128K时,会调用mmap函数在esp到break brk之间找一块相应大小的区域作为内存映射段返回给用户。
当小于128K时,才会调用brk或者sbrk函数,将break brk向上增长(break brk指针向高地址移动)相应大小,增长出来的区域便作为内存返回给用户。
两者的区别是
内存映射段销毁时,会释放其映射到的物理内存,
而break brk指向的数据被销毁时,不释放其物理内存,只是简单将break brk回撤,其虚拟地址到物理地址的映射依旧存在,这样使的当再需要分配小额内存时,只需要增加break brk的值,由于这段虚拟地址与物理地址的映射还存在,于是不会触发缺页中断。只有在break brk减少足够多,占据物理内存的空闲虚拟内存足够多时,才会真正释放它们。
栈堆的区别

- 产生碎片不同 对堆来说,频繁的new/delete或者malloc/free势必会造成内存空间的不连续,造成大量的碎片,使程序效率降低。
对栈而言,则不存在碎片问题,因为栈是先进后出的队列,永远不可能有一个内存块从栈中间弹出。
设计考虑
- 代码段和数据段分开,运行时便于分开加载,在哈佛体系结构的处理器将取得更好得流水线效率。
- 代码时依次执行的,是由处理器 PC 指针依次读入,而且代码可以被多个程序共享,数据在整个运行过程中有可能多次被调用,如果将代码和数据混合在一起将造成空间的浪费。
- 临时数据以及需要再次使用的代码在运行时放入栈中,生命周期短,便于提高资源利用率。
- 堆区可以由程序员分配和释放,以便用户自由分配,提高程序的灵活性。
缓冲区溢出攻击(代码注入攻击
- 缓冲区溢出(Buffer Overflow)是一种常见的软件漏洞,它发生在程序中使用缓冲区(一块内存区域)来存储数据时,输入的数据超过了缓冲区的容量,导致多余的数据溢出到相邻的内存区域。
- 常见栈上分配空间,然后溢出直接覆盖前面的返回地址,使得返回到任意代码片段执行。如果开启了栈上执行代码,甚至能栈上注入代码并执行。
虚拟内存
vmtouch 可以查看内存中加载的磁盘页表
用户进程内存空间,也是系统内核分配给该进程的VM(虚拟内存),但并不表示这个进程占用了这么多的RAM(物理内存)。这个空间有多大?命令top输出的VIRT值告诉了我们各个进程内存空间的大小(进程内存空间随着程序的执行会增大或者缩小)。
Linux虚拟地址空间分布
虚拟地址空间在32位模式下它是一个4GB的内存地址块。在Linux系统中, 内核进程和用户进程所占的虚拟内存比例是1:3,如下图。而Windows系统为2:2(通过设置Large-Address-Aware Executables标志也可为1:3)。这并不意味着内核使用那么多物理内存,仅表示它可支配这部分地址空间,根据需要将其映射到物理内存。

值得注意的是,每个进程的内核虚拟地址空间都是映射到相同的真实物理地址上,因为都是共享同一份物理内存上的内核代码。除此之外还要注意内核虚拟地址空间总是存放在虚拟内存的地址最高处。
其中,用户地址空间中的蓝色条带对应于映射到物理内存的不同内存段,灰白区域表示未映射的部分。这些段只是简单的内存地址范围,与Intel处理器的段没有关系。
上图中Random stack offset和Random mmap offset等随机值意在防止恶意程序。Linux通过对栈、内存映射段、堆的起始地址加上随机偏移量来打乱布局,以免恶意程序通过计算访问栈、库函数等地址。
execve(2)负责为进程代码段和数据段建立映射,真正将代码段和数据段的内容读入内存是由系统的缺页异常处理程序按需完成的。另外,execve(2)还会将BSS段清零。
top
VIRT = SWAP + RES # 总虚拟内存=动态 + 静态
RES >= CODE + DATA + SHR. # 静态内存 = 代码段 + 静态数据段 + 共享内存
MEM = RES / RAM
DATA CODE RES VIRT
before allocation: 124 4 408 3628
after 5MB allocation: 5008 4 476 8512 //malloc 5M, DATA和VIRT增加5M, RES不变
after 2MB initialization: 5008 4 2432 8512 //初始化 2M, DATA和VIRT不变, RES增加2M
//如果最后加上free(data), DATA, RES, VIRT又都会相应的减少,回到最初的状态
top 里按f 可以选择要显示的内容。
SWAP
- Swapping的大部分时间花在数据传输上,交换的数据也越多,意味时间开销也随之增加。对于进程而言,这个过程是透明的。
- so(swap out):由于RAM资源不足,PFRA会将部分匿名页框的数据写入到交换区(swap area),备份之。
- si(swap in) : 当发生内存缺页异常的时候,缺页异常处理程序会将交换区(磁盘)的页面又读回物理内存。
- 每次Swapping,都有可能不只是一页数据,不管是si,还是so。Swapping意味着磁盘操作,更新页表等操作,这些操作开销都不小,会阻塞用户态进程。所以,持续飚高的si/so意味着物理内存资源是性能瓶颈。
- 在内存空间设计早期只有分段没有分页时,SWAP还可以用来内存交换(暂存内存数据,重新排列内存),来消除内存碎片。
需要进一步的研究学习
暂无
遇到的问题
暂无
开题缘由、总结、反思、吐槽~~
参考文献
Light-weight Contexts: An OS Abstraction for Safety and Performance
https://blog.csdn.net/zy986718042/article/details/73556012
https://blog.csdn.net/qq_38769551/article/details/103099014
https://blog.csdn.net/ywcpig/article/details/52303745
https://zhuanlan.zhihu.com/p/23643064
https://www.bilibili.com/video/BV1N3411y7Mr?spm_id_from=444.41.0.0
PythonRegex
pattern
^ 匹配字符串的开头
$ 匹配字符串的末尾。
. 匹配任意字符,除了换行符
a| b 匹配a或b
[a-zA-Z0-9] 匹配任何字母及数字
\d 匹配数字。等价于[0-9]。
[aeiou] 匹配中括号内的任意一个字母
[^aeiou] 除了aeiou字母以外的所有字符
\w 匹配包括下划线的任何单词字符。等价于'[A-Za-z0-9_]'。
(\s*) 或者 ([\t ]*) 来匹配任意TAB和空格的混合字符
\s 匹配任何空白字符,包括空格、制表符、换页符等等。等价于 [ \f\n\r\t\v]。
\S 匹配任何非空白字符。等价于 [^ \f\n\r\t\v]。
\b 匹配一个单词边界,也就是指单词和空格间的位置。例如, 'er\b' 可以匹配"never" 中的 'er',但不能匹配 "verb" 中的 'er'。
\B 匹配非单词边界。'er\B' 能匹配 "verb" 中的 'er',但不能匹配 "never" 中的 'er'。
重复
re* 匹配0个或多个的表达式。
re+ 匹配1个或多个的表达式。
re? 匹配0个或1个由前面的正则表达式定义的片段,非贪婪方式
re{ n} 精确匹配 n 个前面表达式。
例如, o{2} 不能匹配 "Bob" 中的 "o",
但是能匹配 "food" 中的两个 o。
re{ n,} 匹配 n 个前面表达式。
例如, o{2,} 不能匹配"Bob"中的"o",
但能匹配 "foooood"中的所有 o。
"o{1,}" 等价于 "o+"。
"o{0,}" 则等价于 "o*"。
re{ n, m} 匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式
match exactlly str
# find should use \ to represent the (6|12|3)
$ find ~/github/gapbs/ -type f -regex '.*/kron-\(6\|12\|3\).*'
/staff/shaojiemike/github/gapbs/kron-12.wsg
/staff/shaojiemike/github/gapbs/kron-3.sg
/staff/shaojiemike/github/gapbs/kron-3.wsg
/staff/shaojiemike/github/gapbs/kron-6.sg
/staff/shaojiemike/github/gapbs/kron-6.wsg
re.match与re.search的区别
re.match只匹配字符串的开始,如果字符串开始不符合正则表达式,则匹配失败,函数返回None;
而re.search匹配整个字符串,直到找到一个匹配。
re.match函数
从字符串的起始位置匹配
flags
多个标志可以通过按位 OR(|) 它们来指定。如 re.I | re.M被设置成 I 和 M 标志:
group
matchObj = re.match( r'(.*) are (.*?) .*', line, re.M|re.I)
if matchObj:
print "matchObj.group() : ", matchObj.group()
print "matchObj.group(1) : ", matchObj.group(1)
print "matchObj.group(2) : ", matchObj.group(2)
else:
print "No match!!"
打印部分内容
re.sub 替换
findall
返回元组,可以指定开始,与结束位置。
result = re.findall(r'(\w+)=(\d+)', 'set width=20 and height=10')
print(result)
# [('width', '20'), ('height', '10')]
实例:objdump结果只提取汇编的命令
import re
# 打开x86汇编代码文件
with open(assembly) as f:
# 读取文件内容
content = f.read()
# 使用正则表达式匹配所有汇编指令,
pattern = r'\b([a-zA-Z]{3,6})\b.*'
# 匹配pattern,但是只将()内结果保存在matches中
matches = re.findall(pattern, content)
# 输出匹配结果
for match in matches:
print(match)
re.split
需要进一步的研究学习
暂无
遇到的问题
暂无
开题缘由、总结、反思、吐槽~~
参考文献
https://blog.csdn.net/weixin_39594191/article/details/111611346
https://www.runoob.com/python/python-reg-expressions.html
Library GLIBC
GLIBC
GLIBC(GNU C Library)是Linux系统中的标准C库,它提供了许多与程序执行和系统交互相关的功能。GLIBC是应用程序与操作系统之间的接口,提供了许多系统调用的包装函数和其他基础功能,使应用程序能够访问操作系统提供的服务和资源。
GLIBC的主要功能包括:
- 标准C函数:GLIBC实现了C语言的标准库函数,例如字符串处理、内存操作、文件操作、数学函数等。它为应用程序提供了基础的编程功能和操作接口。
- 系统调用封装:GLIBC封装了许多底层的系统调用,例如文件I/O、进程管理、网络通信等。它提供了更高级别的API函数,使开发者能够更方便地进行系统编程。
- 内存管理:GLIBC提供了内存分配和管理的函数,例如malloc、free、realloc等。这些函数允许应用程序在运行时动态分配和释放内存,提供了对内存资源的灵活控制。
- 多线程支持:GLIBC提供了对多线程编程的支持,包括线程创建、同步原语、线程局部存储等功能。它使得开发者能够编写多线程的并发程序。
与上下文切换开销的关系
上下文切换与GLIBC之间没有直接关系。上下文切换是操作系统的概念,是在进程或线程之间切换执行权的过程。GLIBC作为C库,封装了一些系统调用和基础功能,但并不直接参与上下文切换的过程。
然而,GLIBC的性能和效率可以影响上下文切换的开销。GLIBC的实现方式、性能优化以及与操作系统内核的协作方式,可能会对上下文切换的效率产生影响。例如,GLIBC的线程库(如pthread)的设计和性能特性,以及对锁、条件变量等同步原语的实现方式,都可能会影响多线程上下文切换的开销。
因此,尽管GLIBC本身不直接执行上下文切换,但它的设计和实现对于多线程编程和系统性能仍然具有重要的影响。
安装最新版本
ubuntu换源
在PPA。改系统的glibc十分的危险,ssh连接,ls命令等,都需要用到。会导致ssh连接中断等问题。
从源码安装
不推荐,可能会遇到库依赖。比如缺少bison和gawk。详细依赖见
mkdir $HOME/glibc/ && cd $HOME/glibc
wget http://ftp.gnu.org/gnu/libc/glibc-2.32.tar.gz
tar -xvzf glibc-2.32.tar.gz
mkdir build
mkdir glibc-2.32-install
cd build
~/glibc/glibc-2.32/configure --prefix=$HOME/glibc/glibc-2.32-install
make
make install
寻找动态链接库
您可以使用以下方法来查找libstdc++库的位置:
- 使用
g++或gcc命令查找:如果您的系统上安装了g++或gcc编译器,您可以使用以下命令来查找libstdc++库的位置:
或者
- 使用
ldconfig命令查找:ldconfig是Linux系统中用于配置动态链接器的命令。您可以运行以下命令来查找libstdc++库的路径:
- 在默认路径下查找:libstdc++通常位于标准的系统库路径中。在大多数Linux发行版中,libstdc++的默认安装路径为
/usr/lib或/usr/lib64。您可以在这些目录中查找libstdc++的库文件。
如果您找到了libstdc++库的路径,您可以将其添加到CMakeLists.txt中的CMAKE_CXX_FLAGS变量中,如之前的回答中所示。
请注意,如果您正在使用的是Clang编译器(clang++),则默认情况下它将使用libc++作为C++标准库,而不是libstdc++。如果您确实希望使用libstdc++,需要显式指定使用-stdlib=libstdc++标志。例如:
参考文献
上面回答部分来自ChatGPT-3.5,没有进行正确性的交叉校验。
无
llvm
llvm
LLVM项目开始于一种比Java字节码更低层级的IR,因此,初始的首字母缩略词是Low Level Virtual Machine。它的想法是发掘低层优化的机会,采用链接时优化。

插一嘴:链接时优化
- GCC也支持链接时优化,称为LTO(Link Time Optimization),通过把多个编译单元中分别生成的目标文件在链接时进行全局的优化,可以提高程序的执行效率。
- 具体内容:大幅度减少可执行文件的体积
- 冗余代码和变量/函数的消除:对于在多个模块中出现的相同代码/变量/函数,链接时优化可以将它们合并,从而减少可执行文件的体积,提高程序的执行效率。
- 内联函数:将函数调用直接替换为函数本身的代码,从而减少函数调用的开销,提高程序的执行效率。
- 循环展开和向量化:内联函数后,或许能进一步将循环展开和向量化,从而减少循环体内的分支判断,优化程序的执行效率。
IR:gcc与llvm的区别
学过编译原理的人都知道,编译过程主要可以划分为前端与后端:
- 前端把源代码翻译成中间表示 (IR)。
- 后端把IR编译成目标平台的机器码。当然,IR也可以给解释器解释执行。
经典的编译器如gcc:在设计上前端到后端编写是强耦合的,你不需要知道,无法知道,也没有API来操作它的IR。
- 好处是:因为不需要暴露中间过程的接口,编译器可以在内部做任何想做的平台相关的优化。
- 坏处是,每当一个新的平台出现,这些编译器都要各自为政实现一个从自己的IR到新平台的后端。
- 甚至如果当一种新语言出现,且需要实现一个新的编译器,那么可能需要设计一个新的IR,以及针对大部分平台实现这个IR的后端。
-
如果有M种语言、N种目标平台,那么最坏情况下要实现 M*N 个前后端。这是很低效的。
-
LLVM的核心设计了一个叫 LLVM IR 的通用中间表示, 并以库(Library) 的方式提供一系列接口, 为你提供诸如操作IR、生成目标平台代码等等后端的功能。
- 在使用通用IR的情况下,如果有M种语言、N种目标平台,那么最优情况下我们只要实现 M+N 个前后端。
llvm IR
- LLVM IR 中间表示是适用于多种编程语言的通用中间表示,支持C、C++、Objective-C、Swift、Java、Python等多种编程语言。
- 它是一种低级别的语言,类似于汇编语言,但比汇编语言更高级,包含了类型、变量、函数、控制流等高级语言的特性。
- LLVM编译器可以将多种编程语言编译成LLVM IR,从而可以在LLVM IR层面进行各种优化处理,再将LLVM IR转换为目标平台的机器码。
- 比如要将Python脚本编译成LLVM IR中间表示,可以使用Python LLVM编译器llvmlite和numba。
LLVM IR表示与转换
LVM IR实际上有三种表示:
- .ll 格式:人类可以阅读的文本。
- .bc 格式:适合机器存储的二进制文件。
- 内存表示
各种格式是如何生成并相互转换:
| 格式 | 转换命令 |
|---|---|
| .c -> .ll | clang -emit-llvm -S a.c -o a.ll |
| .c -> .bc | clang -emit-llvm -c a.c -o a.bc |
| .ll -> .bc | llvm-as a.ll -o a.bc |
| .bc -> .ll | llvm-dis a.bc -o a.ll |
| .bc -> .s | llc a.bc -o a.s |
对于LLVM IR来说,.ll文件就相当于汇编,.bc文件就相当于机器码。 这也是llvm-as和llvm-dis指令为什么叫as和dis的缘故。
llvm 前端

clang实现的前端包括
- 词法分析(识别标记)
- 处理源代码的文本输入,将语言结构分解为一组单词和标记,去除注释、空白、制表符等。每个单词或者标记必须属于语言子集,语言的保留字被变换为编译器内部表示。
- 词法分析报错的例子包括:拼写错误、注释没有正确结束、字符串没有正确结束等。
- 语法分析(标记结构完整)
- 语法分析器会根据语法规则验证程序的正确性,如缺少右括号、是否缺少关键字、变量未定义、函数应该有返回值等。
- 语义分析(有无语义矛盾)
- 借助符号表检验代码没有违背语言类型系统。这个表存储标识符(符号)和它们各自的类型之间的映射,以及其它内容。
- 类型检查的一种直觉的方法是,在解析之后,遍历AST的同时从符号表收集关于类型的信息。
- 例子:定义了两个变量 a 冲突。
llvm 后端
见 llvm Backend 一文
clang
clang 与 llvm的关系
Clang 是 LLVM 项目中的一个 C/C++/Objective-C 编译器,它使用 LLVM 的前端和后端进行代码生成和优化。它可以将 C/C++/Objective-C 代码编译为 LLVM 的中间表示(LLVM IR),然后进一步将其转换为目标平台的机器码。Clang 拥有很好的错误信息展示和提示,支持多平台使用,是许多开发者的首选编译器之一。同时,Clang 也作为 LLVM 项目的一个前端,为 LLVM 的生态系统提供了广泛的支持和应用。
clang 的开发与苹果公司的关系
Clang 的开发起源于苹果公司的一个项目,即 LLVM/Clang 项目。在 2005 年,苹果公司希望能够使用一种更加灵活、可扩展、优化的编译器来替代 GCC 作为其操作系统 macOS (Mac OS X) 开发环境的默认编译器。由于当时的 GCC 开发被其维护者们认为变得缓慢和难以维护,苹果公司决定开发一款新的编译器,这就是 Clang 诞生的原因。Clang 的开发团队由该项目的创立者 Chris Lattner 领导,他带领团队将 Clang 发展为一款可扩展、模块化、高效的编译器,并成功地将其嵌入到苹果公司的开发工具链 Xcode 中,成为了 macOS 开发环境中默认的编译器之一。
Clang 是一个开源项目,在苹果公司的支持下,Clang 的开发得到了全球各地的开发者们的广泛参与和贡献。现在,Clang 成为了 LLVM 生态中的一个重要组成部分,被广泛地应用于多平台的编译器开发中。
clang-cannot-find-iostream
Clang and Clang++ "borrow" the header files from GCC & G++. It looks for the directories these usually live in and picks the latest one. If you've installed a later GCC without the corresponding G++, Clang++ gets confused and can't find header files. In your instance, for example, if you've installed gcc 11 or 12.
You can use clang-10 -v -E or clang++-10 -v -E to get details on what versions of header files it's trying to use.
安装g++-12解决
常用工具
github/tools目录下有许多实用工具
llvm-as:把LLVM IR从人类能看懂的文本格式汇编成二进制格式。注意:此处得到的不是目标平台的机器码。llvm-dis:llvm-as的逆过程,即反汇编。 不过这里的反汇编的对象是LLVM IR的二进制格式,而不是机器码。opt:优化LLVM IR。输出新的LLVM IR。llc:把LLVM IR编译成汇编码。需要用as进一步得到机器码。lli:解释执行LLVM IR。
需要进一步的研究学习
暂无
遇到的问题
暂无
开题缘由、总结、反思、吐槽~~
参考文献
文章部分内容来自ChatGPT-3.5,暂时没有校验其可靠性(看上去貌似说得通)。