#include<stdio.h>
#include<string.h>
#include<stdlib.h>
void main()
{
char *p1=(char *)malloc(1024); //1k
char *p2=(char *)malloc(4096); //4k
memset(p2,0,4096);
char *p3=(char *)malloc(8192); //8k
char *p4=(char *)malloc(128*1024*1024); //128k
char *p5=(char *)malloc(115*1024);//115k
printf("p1=%p p2=%p p3=%p p4=%p p5=%p\n",p1,p2,p3,p4,p5);
memset(p2,'1',20);
free(p2);
memset(p2,'y',10);
printf("p2=%s\n",p2);
free(p3); //core
free(p5);
free(p1);
}
输出: p1=0x1c60010 p2=0x1c60420 p3=0x1c61430 p4=0x7ffbabc2b010 p5=0x1c63440
p2=yyyyyyyyyy?
Segmentation fault (core dumped)
1.p2-p2 为啥不是 1024
2.p4 的地址为啥和其它的不一样
3.为啥在 free(p2)之后还能读写 p2
4.为啥 p2 的打印不是 yyyyyyyyyy1111111111
5.为啥在 free(p3)的时候会 core
1
andrewhxism 2018-09-25 17:40:33 +08:00
这代码写的。。。
|
2
zmj1316 2018-09-25 18:14:30 +08:00
考这种未定义行为不应该指定编译器和运行环境么......
|
3
innoink 2018-09-25 18:25:45 +08:00 9
你以为你在问 c 语言,其实你在问 libc 和操作系统
|
4
besto 2018-09-25 18:25:51 +08:00
0, void main 这波可以
1, 买看懂, p2-p2? p2-p1 可能是因为 fence memory 2,>128K malloc 会调用 mmap, 另外地址样式和编译器 甚至 32bit/64bit 都有关系 3, 没有说 free 过后就立刻不可用,甚至这是链表操作的一种偷懒形式 4, 这个其实还取决于编译器, 甚至可能只有 10 个 yy 5, 其实和 4 有关联,个人猜测是因为 free 之后,会修改一个链表值, free 之后再用 memset 把 p3 的数据破坏了. |
5
easylee 2018-09-25 18:29:22 +08:00 via Android
手机上代码没有格式化,可阅读性非常差,不如利用一些贴代码网站。
|
6
zmj1316 2018-09-25 18:33:10 +08:00 1
根本不是考 C,都是操作系统的东西把。。。
1. malloc 会额外记录长度什么的数据,肯定不对齐的 2. 看 MMAP_THRESHOLD,超了就 mmap 去了 3. 虚拟内存按 page 分配,估计 p2 的那个 page 还在所以没抛异常 4.5 和操作系统内存分配链表有关,估计是写 p2 的时候把链表写坏了,VS 里面表现不一样,懒得看了 |
7
JeffKing 2018-09-25 18:40:32 +08:00
1. p2 为啥不是 1024?
不知道你在说什么 2.p4 的地址为啥和其它的不一样 p4 分配的地址过大,导致系统无法分配一整块内存 3.为啥在 free(p2)之后还能读写 p2 free 只是 free 了 p2 指向的 heap 空间,而 p2 本身没有置空,当然可以读写。只不过这时候是越界读写。 4.为啥 p2 的打印不是 yyyyyyyyyy1111111111 因为 p2 已结被 free 了,然后越界写入 y 10,当然是 yyyyyyyy 5.为啥在 free(p3)的时候会 core p2 越界读写写到了 p3 的指针头地址,导致 free(p3)出错 |
8
iceheart 2018-09-25 18:43:32 +08:00 via Android
楼主注释写错了
|
10
where2go 2018-09-25 18:45:33 +08:00
没有 void main() 这种写法, 后面没看
|
11
zhicheng 2018-09-25 18:48:32 +08:00 via iPhone
答案只有一个,未定义行为。
在未定义的前提下讨论代码行为没有任何意义。 |
12
catror 2018-09-25 18:50:17 +08:00 via Android 1
你问的是 ptmalloc(libc 里面的)的分配策略
|
13
fcten 2018-09-25 18:50:27 +08:00
只要我换一个内存分配的实现,你的问题可能都不成立。
|
15
GeruzoniAnsasu 2018-09-25 19:27:09 +08:00 1
你以为你在问 C
我以为你在问 CTF 题怎么做 |
16
lance6716 2018-09-25 20:33:32 +08:00
mark,等答案
|
17
yulon 2018-09-25 20:50:48 +08:00
看见 void main 就不想看了
|
18
huluhulu 2018-09-25 21:06:06 +08:00 via iPhone
void main 很正常……
|
19
zwh2698 2018-09-25 21:07:38 +08:00 via Android
敢问兄弟写了几年?另外兄弟 c 中可以存任意基本类型的类型是什么?
|
20
zwh2698 2018-09-25 21:18:54 +08:00 via Android
还有兄弟你的问题就是问题
|
22
WordTian 2018-09-25 22:10:08 +08:00 via Android
1.分配的空间除了 1024,还有一段存储分配空间元数据的空间
2.p4 分配的空间太大,不是从快表(?)分配,具体怎么分的忘了,总之和小空间不连在一块的 3.好像是将 p2 所在块添加到空表,但 p2 的指针还在 4.p2 用 memset 赋值时给尾部 /0,printf 输出时到 /0 终止 5.可能是第一次给 p2 赋值的时候,在尾部添加了 /0,覆盖了 p3 块的元数据,导致报错 不知道我的答案对不对,只是之前了解 linux 堆的时候看了点资料,凭印象来扯淡 |
23
yankebupt 2018-09-25 22:54:40 +08:00
关于 3 4 5 题
看了一个来源 blackhat.com 的<draft>文档可能是关于某种 heap 实现的,里面这么讲的 首先 1k 以上 128k 以下不属于 fastbin,但也没有用 mmap(应该) 说法貌似是说 free()了之后内存因为不够对齐,并没有立刻交还给 OS,而是原 userdata 头部部分填上了用于遍历 free 列表的前进后退两个指针,同时后面的部分有可能被清空了。 这就导致写 yyy 的时候覆盖了指针...同时导致后面的任意 free 失效... 两点疑问:第一这种 heap 尤其是 free()实现方式会不会很小众... 第二:理论上不 free p3,free p4 的话会不会有可能不会 core? |
24
lolcat 2018-09-26 00:42:16 +08:00 1
0.void main()没错,只不过为了程序规范性和可移植性,c99 建议使用 int main();
1.p2-p2 等于 0 ; 2.malloc 时,系统会自动在堆中找一块连续的内存,只要长度够就行,没规定必须紧挨着前面申请的内存空间,而且在申请的空间的前几个字节是有存储数据的,比如存储了这块空间的大小; 3.free(p2)只是告诉系统 p2 申请的空间可以重新新被申请了,里面的数据没有被清空,但如果继续对 p2 指向的空间进行操作就是操作野指针; 4.我觉得不是因为 free(p3)时报的段错误,在你打印野指针 p2 时就会报段错误; 5.长年战斗在 c 上的楼主连这些问题都没弄懂,我现在连工作都找不到,哎,实在是揪心啊。 |
25
lolcat 2018-09-26 00:55:51 +08:00
试了一下,果然是在 printf 这句出现段错误的,不是在 free(p3)时
|
26
crazyneo 2018-09-26 06:17:47 +08:00 1
都是些操作系统和实现库相关的东西。
1. 这和 malloc 实现有关,tcmalloc jemalloc ptmalloc dlmalloc 实现各有不同,这个问题没意义。 2. linux 下超过 128m 分配在映射区,这个也和操作系统有关,aix6 上并不存在类似限制,这个问题没意义。 3. 还是和 malloc/free 实现有关,free 之后并不是马上被回收,以及看编译器参数,比如 clang 带上-address 参数,直接就 coredump 了,这个问题仍然没意义。 4 和 5 都是针对被释放内存的瞎搞。 这几个给学生做考试题都非常的不规范,你好歹写个 ··· int a[10]; memset(a, 1, sizeof(a)); ··· 类似这种 |
34
dingzs3 OP @zwh2698 8 年了,我觉得是指针指向的内存,你可以存任意类型,这个内存想用什么类型操作都可以告诉编译器,或者自己直接用指针+加偏移来弄
|
35
dingzs3 OP |
38
shilyx 2018-09-26 09:20:52 +08:00
1.p2-p1 为啥不是 1024
malloc 分配了只管在大小范围内用,malloc 也是人实现的,不需要保证任何顺序 2.p4 的地址为啥和其它的不一样 malloc 在原来的地方分不出来了,或者不愿在老地方分配了导致的;无论分配的大小,都可能出现 3.为啥在 free(p2)之后还能读写 p2 malloc 之后保证可以读写,但是 free 或没 malloc 的,不保证读写,也不保证不可读写,可能能读写也可能不能读写,也可能只能读,也可能只能写 4.为啥 p2 的打印不是 yyyyyyyyyy1111111111 可能是也可能不是,malloc 本身有一部分私有内容管理分配信息,不知道他会出现在何处; free 了之后甚至整个堆都有可能消失,说不准 5.为啥在 free(p3)的时候会 core p3 core 了说明已被破坏,但是这是说不准的,不是必然被破坏 |
40
zmj1316 2018-09-26 09:26:38 +08:00
|
41
dingzs3 OP @JeffKing 写错了是 p2-p1 的结果为啥不是 1024
这是 glibc 的内存分配机制问题,小内存是调用的 brk 吧,大内存用的是 mmap,brk 直接对应内核做 e_data 的偏移,所以小内存的虚拟地址是 data 段增长的,而大内存的地址则是在堆栈之间获取一个地址用 此时 p2 的内存对应的物理内存是没有释放的,所以可以读写,且虚拟地址也是在用户空间的。 |
43
where2go 2018-09-26 10:09:11 +08:00
@dingzs3
$ cat >> a.c << "EOF" void main(){} EOF $ gcc -Wall a.c a.c:1:6: warning: return type of 'main' is not 'int' [-Wmain] void main(){} ^~~~ |
45
ioth 2018-09-26 10:40:47 +08:00
c 的技巧似乎在 java 里面没有用了。
|
46
reus 2018-09-26 11:34:33 +08:00
全都是未定义行为,有什么好讨论的。
malloc / free 的行为和内存分配器的实现有关,换一个实现或者换一个版本,可能就不一样了,glibc 就能保证一样?不能,因为没有谁说了保证,那你就不能依赖它,因为它是未定义行为。 有些 C/C++ 项目不敢升级编译器版本,就是因为太过于依赖这些未定义行为,导致编译器一升级,这些行为就变了,代码就炸了。 陋习。 |
47
zwh2698 2018-09-26 13:03:48 +08:00 via Android
@dingzs3 8 年坚持不错啦,我也是 c++出身,后来更多 hybrid. 其实答案是 unoin, 因为编译器一直有一个思想就是编译时更多的发现问题,指针太暴力。其实这个也是其他语言 var 关键字模拟,当然 c++中的 var 是另一回事了。希望坚持,底层开发来钱慢,难度大,要么灰产/黑,要么坚持系统编程。
|
51
bluefalconjun 2018-09-26 15:26:25 +08:00
|
52
myself659 2018-09-26 15:41:02 +08:00
这级别过了不前前前公司的编程考试
|
53
dingzs3 OP @bluefalconjun
@myself659 这个不是为了实现什么功能,也不是实际生产环境用的,只是随便写的程序来验证自己对于知识的理解对不对而已啊,可能一般的 C 开发人员不需要理解这些,就比如 go,它就是尽量屏蔽对于底层的了解,性能也不算太低,开发效率很高。但是在有些场景下,比如 DNS 服务器开发(我现在干的),面对的要求是单机几百万 QPS 的并发,那么我们就必须了解自己的代码是怎么运行的,使用的库是如何实现的,操作系统层面是怎么做的,如何让 CPU 更好的并发执行。场景不一样吧。 但是一旦你理解了这些,那么学其它语言就相当容易了,比如 go,你就能理解它的内存回收机制,他的基本数据类型是如何实现的,它的 goroutine,chan 底层是啥等等。 |
54
zhicheng 2018-09-26 17:25:27 +08:00 via iPhone
你的这个代码,不是什么底层,也不是什么知识,更不能在生产环境使用,除了那种要求指出错误的面试题,不应该用在任何地方。C 语言里明确表明了是未定义行为,就不要花时间去研究如果这样做了会是什么样的结果,因为它真的可以是任何行为,C 语言是一个标准并不是一个 C 实现。
初级工程师总觉得“了解”一点儿别人不知道的很厉害,殊不知这是高级工程师尽量避免的情况。 未定义行为的代码 == 错的代码 |
56
dingzs3 OP |
57
zhicheng 2018-09-26 17:51:39 +08:00 via iPhone
我并没有争论,我只是在说你在误人子弟。
|
58
dingzs3 OP @zhicheng 好吧,我错了,我问个问题已经变成误人子弟了,难道我出个题应该这样说:懂 glibc 和内核原理的人进来,不懂的别看。技术上没有啥不能讨论的啊,了解的可以讨论一下,不了解的可以略过,或者是看着乐呵乐呵。
|
59
zhicheng 2018-09-26 18:07:28 +08:00 via iPhone
因为你的代码本来就是错的,你在为错的代码强行找原因,你今天的 glibc 版本是这个行为,下一个可能是另一个行为,甚至不同的编译参数产生的行为也不一样。你要讨论 glibc 和 linux 就直接讨论,出个错的题目要“考”一下别人也是有趣。
如同法律上写了红灯不要过马路,你非要去讨论什么样的情况下红灯可以过马路,抱歉我理解不了这种行为。 |
60
dingzs3 OP @zhicheng 可能是我表述的有问题吧,我没有考的意思,只是希望讨论一下,这个特意写错的代码只是为了验证库和内核的机制而已,希望拿出来讨论。如果让你这样理解,那我道歉。
|
61
zealot0630 2018-09-26 20:20:28 +08:00
1.p2-p2 为啥不是 1024
glibc 会在 p2 前面用几个字节记录 p2 的分配信息 2.p4 的地址为啥和其它的不一样 p4 用 mmap 分配的,小内存用 brk,大内存用 mmap 3.为啥在 free(p2)之后还能读写 p2 brk 内存分配了就不能释放 4.为啥 p2 的打印不是 yyyyyyyyyy1111111111 p2 还給 glibc 时候,glibc 会吧这段内存返还给链表,p2 这里指向 glibc 内部维护空闲内存的链表 5.为啥在 free(p3)的时候会 core glibc 维护的空闲内存链表被你破坏了,glibc 就崩了 |
62
dingzs3 OP @zealot0630 赞
|
63
zmj1316 2018-09-27 07:24:11 +08:00 via Android
|
64
zhicheng 2018-09-27 09:46:03 +08:00 via iPhone
@zmj1316 工作这么多年,头一回听说用 Ub 的行为来 debug,你知道 ub 这是哪两个词的缩写?调试内存问题,你可以用 valgrind 可以用 addresssanitizer,能通过 ub 的行为来定位 bug ?我猜你是神。
|
65
zmj1316 2018-09-27 10:07:55 +08:00
@zhicheng 兄弟语文没学好啊...我说的是因为用了 UB 出现 bug,没说用 UB 来 debug 啊......我又不是来抬杠的
|
66
zmj1316 2018-09-27 10:12:28 +08:00
@zhicheng 讲的明白一点
`如同法律上写了红灯不要过马路,你非要去讨论什么样的情况下红灯可以过马路,抱歉我理解不了这种行为。` 就是为了明白,路上撞死了个人,不一定是汽车司机故意撞死的,也可能是行人自己闯红灯作死的,划分责任的时候,不要全都怪司机,说不定是行人的责任 |
67
dingzs3 OP @zmj1316 哥们,技术上的问题讨论明白就行了,没必要把问题升华讨论更上层的东西,已经结贴了,本来想删除的,但是不知道怎么删除,而且有十几个人收藏了。技术讨论最好永远局限于技术,也多谢你帮忙解释。
|
68
zhicheng 2018-09-27 10:24:12 +08:00 via iPhone
@zmj1316 你说的那不是废话吗?用了 UB 基本上都是要出 Bug 的,大部分 Bug 也是由 UB 引起的,是我语文没学好,还是你逻辑没学好?还是知道 UB 会出现 Bug 是件很牛逼的事?
|
69
zmj1316 2018-09-27 10:36:54 +08:00
@dingzs3 抱歉,我的锅,工作不饱和,老老实实搬砖去了,C++写久了 OS 层的东西都快忘光了,感谢 LZ 还让我复习一下 hhhhh
|
71
bluefalconjun 2018-09-27 13:27:52 +08:00
@dingzs3 如果是为了了解相关知识, 真的应该看书 看 spec 看操作系统 /编译器的实现代码...
在这里写这种 code 来了解... 实话说, 做不到的... 就像你写个黑盒的测试代码 想要来理解里面的实现.. 白盒测试都不一定能完成的事情... |
72
wizardforcel 2018-09-27 14:11:19 +08:00 via Android
|
73
wizardforcel 2018-09-27 14:14:42 +08:00 via Android
@zhicheng 首先 glibc 不是一种实现,而是很多种实现,比如 tmalloc。固定了版本号就固定了实现。
其次,就算你不去利用,黑客也会利用。你觉得被黑是很好玩的事情么?? 你以为标准库和编译器都是严格按照标准实现的??你自己搞搞 plt,做做语言律师也就算了,还把这种陋习搬到实际生产中来。你的心真大。呵呵。 |
74
wizardforcel 2018-09-27 14:29:39 +08:00
@zhicheng
> 初级工程师总觉得“了解”一点儿别人不知道的很厉害,殊不知这是高级工程师尽量避免的情况。 我认为正好相反,初级工程师只需要知道那个是 UB,高级工程师需要知道具体实现是怎么回事。 虽然大家都知道不能用 UB,但是,知道了产生的后果并且知道为什么不能用,更深刻一些。 知其然而知其所以然,懂?? |
75
zhicheng 2018-09-27 15:20:12 +08:00 via iPhone
@wizardforcel 刚好相反,高级工程师根本就不会关心 UB 会产生什么样的行为,因为无论它产生什么样的行为,都是错的,哪怕程序看起来正确。
你知道 UB 会产生什么样的行为能避免被黑?程序能更安全?不,能让程序更安全的是从一开始就尽量避免引入 UB。 没有什么实现是完全正确的,但你明显不能往肯定错误的方向走啊。 |
76
aihimmel 2018-09-27 17:03:51 +08:00
@GeruzoniAnsasu 真实堆利用题目
|