线程局部存储(TLS)

什么是线程局部存储?

线程局部存储 (TLS) 是一种存储持续期(storage duration),对象的存储是在线程开始时分配,线程结束时回收,每个线程有该对象自己的实例。这种对象的链接性(linkage)可以是静态的也可是外部的。
WindowsTLS的存储结构为:

静态TLS

静态TLS需要使用 __declspace(thread) 变量 进行标识, 当编译器进行打包时会将它存储到特定的节中(.tls)它将会变成一个tls模板, 在程序进行线程创建时将会开辟一个足够大的空间将tls模板数据复制进去(tls副本), 所以每个线程访问与修改它时互不干扰.

PE加壳带静态TLS如何修复?

这个问题我也是折腾了很久, 在网上找了很久的资料基本上都是讲的TLS Callback,反调试相关的, 然后看了<<Windows PE权威指南>>虽然有说静态TL是如何存储的,但是没有例子,只是说了下 不过已经知道TLS是存储在哪儿的 那不就好办了? 书上说的是
“当创建线程时,加载器通过将线程环境块(TEB)的地址放入FS寄存器来传递线程的TLS数组地址。距TEB开头0x2C的位置处的字段ThreadLocalStoragePointer指向TLS数组” 书上这里说的是32位的, 64位的可以自己去查 TEB相关字段偏移 也就是说只要我将 TLS模板中的数据先写到一块内存中, 然后将数据首地址写入这个数组中就好了?
跟着书上说的我去试了下, 发现有大坑!!!, 然后我仔细想了想了TEB在每个线程都是不一样的, 所以我加进去的只是TLS副本也就是说我只给我当前获取到TEB的那个线程加进去了, 其他的还是没有然后这样会导致我的程序在开新线程的时候获取不到TLS数据… 离谱吧, 所以在程序中不开线程访问是莫问题滴… 但是对于简单的程序还好, 要是遇到网络相关操作的一个线程?? 所以这个算只是解决了半个问题??.. 现在大概是知道问题所在, 我修改的只是TLS副本, 所以我只需要修改加载器的TLS模板数据就可以了? (也就是说将加载器TLS数据模板替换为我主程序的模板数据). 但是我怎么知道加壳程序TLS模板存储位置呢.. 带着这个问题又去查资料, 果然在Github找到一个外国佬写的pe_loader而且它支持静态TLS的加载, 所以我去看了他的代码 tls_support.cpp


他获取这个模板数据首地址是硬搜的, 先拿到TLS表然后去内存匹配
芜湖~ 这不就是我想要的? 好像他的代码挺复杂的, 不过已经知道办法了只需要撸代码了

查看更多

PE头文件学习笔记

可移植可执行

  1. 是一种用于可执行文件、目标文件和动态链接库的文件格式,主要使用在32位和64位的Windows操作系统上。可移植的是指该文件格式的通用性,可用于许多种不同的操作系统和体系结构中。PE文件格式封装了Windows操作系统加载可执行程序代码时所必需的一些信息。这些信息包括动态链接库、API导入和导出表、资源管理数据和线程局部存储数据。在Windows NT操作系统中,PE文件格式主要用于EXE文件、DLL文件、.sys(驱动程序)和其他文件类型。可扩展固件接口(EFI)技术规范书中说明PE格式是EFI环境中的标准可执行文件格式。开头为DOS头部。
  2. PE格式是由Unix中的COFF格式修改而来的。在Windows开发环境中,PE格式也称为PE/COFF格式。

DOS头 winnt.h

结构体信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
WORD e_magic; // * Magic number
WORD e_cblp; // Bytes on last page of file
WORD e_cp; // Pages in file
WORD e_crlc; // Relocations
WORD e_cparhdr; // Size of header in paragraphs
WORD e_minalloc; // Minimum extra paragraphs needed
WORD e_maxalloc; // Maximum extra paragraphs needed
WORD e_ss; // Initial (relative) SS value
WORD e_sp; // Initial SP value
WORD e_csum; // Checksum
WORD e_ip; // Initial IP value
WORD e_cs; // Initial (relative) CS value
WORD e_lfarlc; // File address of relocation table
WORD e_ovno; // Overlay number
WORD e_res[4]; // Reserved words
WORD e_oemid; // OEM identifier (for e_oeminfo)
WORD e_oeminfo; // OEM information; e_oemid specific
WORD e_res2[10]; // Reserved words
LONG e_lfanew; // * File address of new exe header
} IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

查看更多

记一次C内存溢出问题

记一次C内存溢出问题

问题代码

1
2
3
4
5
6
7
8
9
10
11
12
#include <stdio.h>

void func(){
char buff[1024 * 1024 * 10];
printf("hello func\n");
}

int main(int argc, char **argv){
func();
return 0;
}

编译后运行直接段错误,查了半天硬是没查出问题, 后突然想到是不是栈空间满了,查了下百度果不其然
在我的Linux下使用ulimit -s查出我的系统栈空间大小为8kb而我开辟了1MB的空间所以出现段错误

解决办法

buff变量加上static关键字或缩小buff大小即可解决问题,还可以将buff作为全局变量

acm-士兵队列训练问题

士兵队列训练问题

某部队进行新兵队列训练,将新兵从一开始按顺序依次编号,并排成一行横队,训练的规则如下:从头开始一至二报数,凡报到二的出列,剩下的向小序号方向靠拢,再从头开始进行一至三报数,凡报到三的出列,剩下的向小序号方向靠拢,继续从头开始进行一至二报数。。。,以后从头开始轮流进行一至二报数、一至三报数直到剩下的人数不超过三人为止。

分析

该题意思是,队列编号第一轮编号1~2叫到 2 的出列, 第二轮编号1~3叫到3的出列,反复循环直到剩余人数不超过3

实现代码 c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
#include <stdio.h>
#include <stdlib.h>


void full(int num, int *soldier){
for (int i = 0; i < num; i++)
{
soldier[i] = i+1;
}
}


int* line_up(int num){
int* soldier = malloc(sizeof(int) * num);
//初始化士兵
full(num, soldier);
/**
* i: 当前士兵
* j: 报数
* s: 最高编号
*/
for (int i = 0, j = 1, s = 2, m = num; m >= 3; i++)
{
if(i > num && s == 2){
/**
* 第一轮报数完成, 进行第二轮报数
*/
s = 3; i = 0; j = 1;
}else if(i > num && s == 3){
/**
* 第二轮报数完成,重新开始到第一轮报数
*/
s = 2, i = 0, j = 1;
}
/**
* 当前士兵已经出列, 继续报数
*/
if(soldier[i] == -1) continue;
if(j == s){
/**
* 出列
*/
soldier[i] = -1;
j = 1; m--;
}else{
j++; // 编号
}
}
return soldier;
}


int out_soldier(int num){
int *soldier = line_up(num);
for (int i = 0; i < num; i++)
{
if(soldier[i] == -1) continue;
printf("%d ", soldier[i]);
}
printf("\n");
free(soldier);
}


int main(){
int row, col;
scanf("%d", &row);
int array[row], i = 0;
while (i < row)
{
scanf("%d", array + i++);
}
i = 0;
while (i < row)
{
out_soldier(array[i++]);
}
return 0;
}

记录一次学习C++时编译失败出现(multiple definition of xxx first defined here collect2)

记录一次学习C++时编译失败出现(multiple definition of xxx first defined here collect2)

log
报错说我重复定义了这个重载函数, 网上查了半天,说是头文件没有写#ifndef & #define可是我明明写了, 还是不起作用,又说使用关键字extern, 可是我就是要定义在头文件里面,后来看了c++的全局重载函数,发现都写了inline修饰,于是试了试结果成功了

解决办法

使用inline修饰这个函数,使用它修饰需要考虑自己的代码是否符合inline

inline相关

在 c/c++ 中,为了解决一些频繁调用的小函数大量消耗栈空间(栈内存)的问题,特别的引入了 inline 修饰符,表示为内联函数。

个人理解

我理解它其实就是和宏定义差不多,宏定义会在编译器编译时进行宏展开,而inline做的事情其实和它是差不多的,只不过inline会有一些使用限制.
然后我这个错误在还未链接时是没有错误的,在链接是就出现了重复定义,使用inline就正好解决了我遇到的问题,因为它在编译时就会把这个函数转换为内联函数,所以在链接时就不会出现重复定义问题

参考资料

查看更多