你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

虚拟内存模型和malloc内部原理

[复制链接]
gaosmile 发布时间:2020-11-12 21:03
摊牌了,不装了,其实我是程序喵辛苦工作一天还要回家编辑公众号到大半夜的老婆,希望各位大哥能踊跃转发,完成我一千阅读量的KPI(梦想),谢谢!
; l" q# N1 O) v
咳咳,有点跑题,以下是程序喵的废话,麻烦给个面子划到最后点击在看或者赞,证明我比程序喵人气高,谢谢!

9 H  G7 ?3 Z3 f) X9 G8 X4 m" X
通过/proc文件系统探究虚拟内存3 d: x# T5 G- Z( m# @; W
我们会通过/proc文件系统找到正在运行的进程的字符串所在的虚拟内存地址,并通过更改此内存地址的内容来更改字符串内容,使你更深入了解虚拟内存这个概念!这之前先介绍下虚拟内存的定义!
虚拟内存
5 g' ?, i$ v, V0 b6 ~
虚拟内存是一种实现在计算机软硬件之间的内存管理技术,它将程序使用到的内存地址(虚拟地址)映射到计算机内存中的物理地址,虚拟内存使得应用程序从繁琐的管理内存空间任务中解放出来,提高了内存隔离带来的安全性,虚拟内存地址通常是连续的地址空间,由操作系统的内存管理模块控制,在触发缺页中断时利用分页技术将实际的物理内存分配给虚拟内存,而且64位机器虚拟内存的空间大小远超出实际物理内存的大小,使得进程可以使用比物理内存大小更多的内存空间。
) V7 K; e9 Q1 j# {; M% U% @7 {
在深入研究虚拟内存前,有几个关键点:
  • 每个进程都有它自己的虚拟内存
  • 虚拟内存的大小取决于系统的体系结构
  • 不同操作管理有着不同的管理虚拟内存的方式,但大多数操作系统的虚拟内存结构如下图:

    # d# S6 E& t/ N$ a4 U4 y
微信图片_20201112205857.png
virtual_memory.png
上图并不是特别详细的内存管理图,高地址其实还有内核空间等等,但这不是这篇文章的主题。从图中可以看到高地址存储着命令行参数和环境变量,之后是栈空间、堆空间和可执行程序,其中栈空间向下延申,堆空间向上增长,堆空间需要使用malloc分配,是动态分配的内存的一部分。
3 ]; q0 N) f4 @
首先通过一个简单的C程序探究虚拟内存。1 ~' b9 \1 V; N3 H- V9 Z# Z, s0 J
#include <stdlib.h>5 u. }1 b) a' J  m  ^0 d, Z
#include <stdio.h>5 [; B$ {9 J- p) j' R. E% L: I" k$ `
#include <string.h>5 H: ?2 r- y& j; P& F+ H$ c
8 R  B/ [. Z7 h! {' k
/**4 i9 m+ [' {9 O4 ^4 ^% I9 s
* main - 使用strdup创建一个字符串的拷贝,strdup内部会使用malloc分配空间,. S0 y# u% X9 H  z
* 返回新空间的地址,这段地址空间需要外部自行使用free释放3 p  R7 ]: I5 V6 V6 Q% n/ H
*
/ b1 M; t, _7 g. u3 n+ O7 u3 |9 U8 k * Return: EXIT_FAILURE if malloc failed. Otherwise EXIT_SUCCESS
2 l/ v4 K$ V+ x */
/ |2 q7 v$ N4 c9 c  Q% ?) v5 mint main(void); g8 {, _8 @7 `2 ^- ~4 p& h' x& U
{
+ n' b! ~. l; t. V3 H6 @% o    char *s;) U; T8 x( R( J! o2 M6 D5 T; J

& ?3 `. |6 ~# d    s = strdup("test_memory");) J4 `  e+ n8 R" p
    if (s == NULL)
- j/ j- n  [4 C( ]    {
0 O$ I+ x8 D, y( ~( ^/ [& e        fprintf(stderr, "Can't allocate mem with malloc\n");; l6 `' z$ o4 G; d& y7 a
        return (EXIT_FAILURE);( D- Q' l5 s' j! g. U8 S) a
    }
+ ?9 q, l' u, v$ v0 P" d8 h    printf("%p\n", (void *)s);+ Z- p9 n- _: l1 l1 I
    return (EXIT_SUCCESS);
* s) P% g8 F( ?1 _) R5 z7 a}
  M+ b) c( p9 v2 q2 Z# t/ l
/ G' k+ [" {9 {# T- J编译运行:gcc -Wall -Wextra -pedantic -Werror main.c -o test; ./test
; r" V% d. a6 K2 I$ Y% M2 |输出:0x88f010
! Z! a; c: \: M- j+ v9 s

我的机器是64位机器,进程的虚拟内存高地址为0xffffffffffffffff, 低地址为0x0,而0x88f010远小于0xffffffffffffffff,因此大概可以推断出被复制的字符串的地址(堆地址)是在内存低地址附近,具体可以通过/proc文件系统验证.3 m4 j& ~, S4 d# _
ls /proc目录可以看到好多文件,这里主要关注/proc/[pid]/mem和/proc/[pid]/maps

mem & mapsman proc
8 R8 ~. M1 ]% I& j4 f
0 H% f: R! w3 k1 p! z$ f/proc/[pid]/mem
8 q! b5 o* a% j    This file can be used to access the pages of a process's memory through open(2), read(2), and lseek(2).
* y5 K! T* ~, m+ n* n) S
6 g2 a' |9 W- E, ?/proc/[pid]/maps
5 A' z2 G4 N7 W' c    A  file containing the currently mapped memory regions and their access permissions.0 O6 T- v/ H& m" G. X; h4 s: i  P
          See mmap(2) for some further information about memory mappings.
5 J  p( e. Y7 b/ i. ~" \' j7 _, Q# s" b; o- Y
              The format of the file is:
  n6 P9 R5 _) ]# j9 z5 ]- U" k* o7 l# ]& \
       address           perms offset  dev   inode       pathname9 R, a8 [- }& E; o2 L
       00400000-00452000 r-xp 00000000 08:02 173521      /usr/bin/dbus-daemon( W: ]/ X: H4 e  i5 u+ W2 p
       00651000-00652000 r--p 00051000 08:02 173521      /usr/bin/dbus-daemon
# j4 }: K5 D0 ^( I6 I+ o       00652000-00655000 rw-p 00052000 08:02 173521      /usr/bin/dbus-daemon
3 {0 h1 B  y2 ^) e       00e03000-00e24000 rw-p 00000000 00:00 0           [heap]
/ s3 |/ \- b& c       00e24000-011f7000 rw-p 00000000 00:00 0           [heap]$ u2 y# O' `" Q* ?4 V
       ...# a9 N; k) T2 R$ u
       35b1800000-35b1820000 r-xp 00000000 08:02 135522  /usr/lib64/ld-2.15.so2 B' X* S8 r: ~# X5 b4 j$ G
       35b1a1f000-35b1a20000 r--p 0001f000 08:02 135522  /usr/lib64/ld-2.15.so
' N4 Y+ C5 p, U       35b1a20000-35b1a21000 rw-p 00020000 08:02 135522  /usr/lib64/ld-2.15.so
  C: Z( k8 \: h6 m- |       35b1a21000-35b1a22000 rw-p 00000000 00:00 0  s* U0 u0 R3 p% d8 j9 O. E
       35b1c00000-35b1dac000 r-xp 00000000 08:02 135870  /usr/lib64/libc-2.15.so: k  e" F5 C6 L  D8 `
       35b1dac000-35b1fac000 ---p 001ac000 08:02 135870  /usr/lib64/libc-2.15.so
: B0 Q+ @; K$ o1 ]+ B! O       35b1fac000-35b1fb0000 r--p 001ac000 08:02 135870  /usr/lib64/libc-2.15.so
8 @% w$ D+ I2 a/ ]) w+ s4 ~5 R       35b1fb0000-35b1fb2000 rw-p 001b0000 08:02 135870  /usr/lib64/libc-2.15.so& q7 p# }3 l" {" e
       ...' K( s7 z9 ~* M
       f2c6ff8c000-7f2c7078c000 rw-p 00000000 00:00 0    [stack:986]1 u0 X* y1 d" w. |4 B
       ...
+ {% E; e% y% L( l7 N. R       7fffb2c0d000-7fffb2c2e000 rw-p 00000000 00:00 0   [stack]# d4 I& }7 ^  F9 ?# E# [; `4 `
       7fffb2d48000-7fffb2d49000 r-xp 00000000 00:00 0   [vdso]
& i9 N1 {% c1 d0 i# C
5 W8 b. o4 _" f              The address field is the address space in the process that the mapping occupies.
  V2 u, F9 C  L" Q  Z' o" o" i          The perms field is a set of permissions:1 o2 t1 Y/ w( E3 K  i* G

. m% l2 n4 G& G. T( K                   r = read- ~$ m/ P1 p% x! j* h5 I7 B
                   w = write% G1 y0 U8 j& S% H9 o9 T9 g1 ?8 S
                   x = execute
* P- d; ^' m( o, e( J4 r                   s = shared# O8 D- w% t7 b" F4 z% K8 E
                   p = private (copy on write)7 p  b2 @& B0 y# d( T
) a4 {' S: \5 t. e. L: _
              The offset field is the offset into the file/whatever;. l( a  y* M2 d/ Y6 h3 p6 |
          dev is the device (major:minor); inode is the inode on that device.   0  indicates
: Y; e) f: D( ?( m" b              that no inode is associated with the memory region,
. r) c8 e9 U' ]" K5 X          as would be the case with BSS (uninitialized data).6 b. L, F/ t6 H# Y: Z& @; q
" Q# F3 n7 \+ V3 Z9 R4 @
              The  pathname field will usually be the file that is backing the mapping.8 u+ o4 m; k& U$ p  A4 [# i
          For ELF files, you can easily coordinate with the offset field7 A! E, ~' R& _7 T
              by looking at the Offset field in the ELF program headers (readelf -l).
& A" Q6 {, \) ~: R0 I7 }, R) l: ]7 R. l: ?+ d) L
              There are additional helpful pseudo-paths:2 G2 N+ s* C$ ]
$ d1 j# d3 v/ Z. q8 ]
                   [stack]
$ Y; `5 w& W6 ^, U& A                          The initial process's (also known as the main thread's) stack.
. P0 o5 S7 y# h5 v
3 k( d% N2 ^4 b9 Y) T                   [stack:<tid>] (since Linux 3.4)
7 t2 S) d& O" q2 b/ E$ p                          A thread's stack (where the <tid> is a thread ID).4 s4 J, i6 B; @" W; ^- u
              It corresponds to the /proc/[pid]/task/[tid]/ path.
- @) M2 c; q, J3 [* V8 K, a9 q3 l
                   [vdso] The virtual dynamically linked shared object.
4 ^  f9 E6 ^$ ^7 V% c+ ^+ T: @+ Y. v& y/ [
                   [heap] The process's heap.6 f3 V$ V6 l. ?7 J

$ B3 t* Z" i0 x              If the pathname field is blank, this is an anonymous mapping as obtained via the mmap(2) function.# A% R" z" q0 V6 L3 k, x
          There is no easy  way  to  coordinate( L' H2 d9 j1 U; p) d; R# P# E( \# w
              this back to a process's source, short of running it through gdb(1), strace(1), or similar.
9 w/ F6 d8 ]$ B
6 M: O9 N: ]. ?$ {. R8 k              Under Linux 2.0 there is no field giving pathname.
- F$ D! H7 m: Z% C: ~# L

通过mem文件可以访问和修改整个进程的内存页,通过maps可以看到进程当前已映射的内存区域,有地址和访问权限偏移量等,从maps中可以看到堆空间是在低地址而栈空间是在高地址.  从maps中可以看到heap的访问权限是rw,即可写,所以可以通过堆地址找到上个示例程序中字符串的地址,并通过修改mem文件对应地址的内容,就可以修改字符串的内容啦,程序:

#include <stdlib.h>! l5 {! n7 n2 H" m! `2 n/ j' J
#include <stdio.h>/ e& k- y2 B# }
#include <string.h>% \% M1 q3 l6 ~8 Z. S
#include <unistd.h>
7 b3 ~! G5 @) B4 M) {- ?# j
! L$ P4 t& M/ {# H/**              " j" J5 l0 z9 s! g9 h1 G3 W; B
* main - uses strdup to create a new string, loops forever-ever/ f8 w8 _- y4 c6 c
*               
0 g% g+ u7 U* H6 w * Return: EXIT_FAILURE if malloc failed. Other never returns
4 \* K- E7 ?# y4 M9 T1 ] */7 A. n" j% R4 d2 G+ y4 T0 K
int main(void)- c4 J" |# i) c- q$ v% S) U
{
8 ^2 B4 U' V( R9 u     char *s;
) f+ B3 B( l2 ~7 ^* G     unsigned long int i;* R0 c$ n- d/ E/ L3 {
7 d/ ]5 y* b" w& H& m1 V! ^2 R
     s = strdup("test_memory");
! w+ }+ I5 m; |/ u8 }) f! s; e     if (s == NULL)
! L( D# a  m7 \8 t2 L% K2 z0 A     {' B- ~9 ]+ `" q
          fprintf(stderr, "Can't allocate mem with malloc\n");
- z% Z& d  C* c" i, Z6 {          return (EXIT_FAILURE);1 a9 T& T6 B7 Q$ C, m9 j
     }
% g9 }" h% J- V9 h- z# e! ]     i = 0;
- X! m5 n8 Z7 K8 |! ~1 b     while (s)
. T& b; J+ p7 `1 M/ l4 c2 Y1 R     {
" u2 ?. y5 \2 O' r( F9 n  P( L" R5 M          printf("[%lu] %s (%p)\n", i, s, (void *)s);0 h# F- l/ l& w8 m( b7 `
          sleep(1);
7 [3 C0 v) s+ R- G, o          i++;
! t! L: |% m5 L' e2 f8 S' L     }& b2 R( w. d, Y- Q3 r# ^* v
     return (EXIT_SUCCESS);9 X% T* o* y7 i$ d& }
}
3 n' f9 ]$ [3 g- ~% H9 b编译运行:gcc -Wall -Wextra -pedantic -Werror main.c -o loop; ./loop
7 [* @$ r1 s- l& Z( d3 g. g% O; `输出:; Z8 g! F( k# m- e0 w! p
[0] test_memory (0x21dc010)# d0 _4 t) x% [
[1] test_memory (0x21dc010)
) p3 z' I0 i4 @8 ]7 t/ x[2] test_memory (0x21dc010)( @# ~8 x$ m/ Q' h8 f
[3] test_memory (0x21dc010)
" [( L  ?1 ]3 Y4 x$ k7 S" i[4] test_memory (0x21dc010); H- P; I, U  }* c
[5] test_memory (0x21dc010)) m  z  B+ j6 \( A0 Z) j, G
[6] test_memory (0x21dc010)
9 J: I+ {. Q; e...
& o+ C! _$ |4 _. a* x7 y6 }; l4 |& p+ h! z  o6 e

这里可以写一个脚本通过/proc文件系统找到字符串所在位置并修改其内容,相应的输出也会更改。


; W  C% l0 p, A首先找到进程的进程号

ps aux | grep ./loop | grep -v grep# Q+ z( R0 ~% p" a, E! |
zjucad    2542  0.0  0.0   4352   636 pts/3    S+   12:28   0:00 ./loop+ J' c* a$ f" c

2542即为loop程序的进程号,cat /proc/2542/maps得到

00400000-00401000 r-xp 00000000 08:01 811716                             /home/zjucad/wangzhiqiang/loop6 y$ Z; R; `7 F% o: B+ b
00600000-00601000 r--p 00000000 08:01 811716                             /home/zjucad/wangzhiqiang/loop- _4 D9 J" H. S- r7 `5 h
00601000-00602000 rw-p 00001000 08:01 811716                             /home/zjucad/wangzhiqiang/loop
9 H: b' h( K! [* j* J3 j021dc000-021fd000 rw-p 00000000 00:00 0                                  [heap]
( f: C4 \8 G+ K; O. Y1 u) W. y7f2adae2a000-7f2adafea000 r-xp 00000000 08:01 8661324                    /lib/x86_64-linux-gnu/libc-2.23.so
% R: P+ n3 {+ `* Q7f2adafea000-7f2adb1ea000 ---p 001c0000 08:01 8661324                    /lib/x86_64-linux-gnu/libc-2.23.so5 p4 t  u* O3 Y/ L" M8 ~. y
7f2adb1ea000-7f2adb1ee000 r--p 001c0000 08:01 8661324                    /lib/x86_64-linux-gnu/libc-2.23.so
; I0 ]5 k5 Y' N$ P( m7f2adb1ee000-7f2adb1f0000 rw-p 001c4000 08:01 8661324                    /lib/x86_64-linux-gnu/libc-2.23.so
% L* j! _! U6 K. s5 \0 d" Y: P7f2adb1f0000-7f2adb1f4000 rw-p 00000000 00:00 03 ~' Y' f& c$ w
7f2adb1f4000-7f2adb21a000 r-xp 00000000 08:01 8661310                    /lib/x86_64-linux-gnu/ld-2.23.so
, {! w$ ~, Q7 l7f2adb3fa000-7f2adb3fd000 rw-p 00000000 00:00 0& o) h7 S7 M' W! s& \; ?' P- D
7f2adb419000-7f2adb41a000 r--p 00025000 08:01 8661310                    /lib/x86_64-linux-gnu/ld-2.23.so
/ }* r& |% t- Y0 K$ v, j7f2adb41a000-7f2adb41b000 rw-p 00026000 08:01 8661310                    /lib/x86_64-linux-gnu/ld-2.23.so
! u7 f6 `9 W7 i! d8 o5 O# z6 Z7f2adb41b000-7f2adb41c000 rw-p 00000000 00:00 0
6 q0 a! l9 X( }9 ^8 J( {9 Y$ w7ffd51bb3000-7ffd51bd4000 rw-p 00000000 00:00 0                          [stack]" x4 v( V9 O- O
7ffd51bdd000-7ffd51be0000 r--p 00000000 00:00 0                          [vvar]" g6 o) p2 X5 T4 A- N6 n
7ffd51be0000-7ffd51be2000 r-xp 00000000 00:00 0                          [vdso]3 @$ T6 f6 w1 Z4 N
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]0 r( Q+ O7 E3 E( |8 j1 C

看见堆地址范围021dc000-021fd000,并且可读可写,而且021dc000<0x21dc010<021fd000,这就可以确认字符串的地址在堆中,在堆中的索引是0x10(至于为什么是0x10,后面会讲到),这时可以通过mem文件到0x21dc010地址修改内容,字符串输出的内容也会随之更改,这里通过python脚本实现此功能.

#!/usr/bin/env python3
5 q5 N! o# B5 [6 ?+ F'''             : n+ }6 [' ^$ }/ \4 n
Locates and replaces the first occurrence of a string in the heap
0 i* s2 A3 [( ^( iof a process    3 D' G, M* w' [: d& j
8 n% ]. |! Q! v) {2 V
Usage: ./read_write_heap.py PID search_string replace_by_string! ~& q- [  u' J6 R( ~0 d
Where:           
* z5 p& b: {1 t0 B% F( l5 U3 J# b- PID is the pid of the target process& ]  s& [% z+ Q! W) d0 [: _# |
- search_string is the ASCII string you are looking to overwrite0 u/ S' R: Y0 X" C" T" T
- replace_by_string is the ASCII string you want to replace/ j6 c/ x' M3 |
  search_string with( q! V' ?. ^' G: c
'''
6 j2 F4 C; j) I/ H- F: W7 p( C" r8 N3 o
import sys
  Q) \: K5 Y3 X; Q, l4 D  q6 k1 T/ j4 e8 a  t$ q( _; i, P% R, F
def print_usage_and_exit():4 V) s7 Q% ]$ e! t/ S
    print('Usage: {} pid search write'.format(sys.argv[0]))
% }" J* q2 @3 V% a( x4 O0 o    sys.exit(1)7 b& F* |$ O  P" f9 G! q
3 x! d; ~( M( u+ E5 o
# check usage  3 z1 p$ Y1 ~  t
if len(sys.argv) != 4:
- N- v$ \3 U' j: z, F: D    print_usage_and_exit()
( ]! v, x5 I! g0 V! e5 ?7 {% R. @, T4 I1 C
# get the pid from args
/ z" H( x: M7 m3 hpid = int(sys.argv[1])
, g, T/ n) m& p4 mif pid <= 0:  s4 X7 c, Q; ^2 \/ ~5 n+ F' B
    print_usage_and_exit()- H4 ~2 c% E7 ^2 \2 j
search_string = str(sys.argv[2])2 T2 p/ }2 i, G' t; y9 s) ~
if search_string  == "":
0 C( h' U; p  X, K2 Q    print_usage_and_exit()
- I0 g! L& j' M$ I6 twrite_string = str(sys.argv[3])
0 a# I- l# m5 {( l& uif search_string  == "":+ F1 o- \; L. r, M8 l
    print_usage_and_exit()+ j# ]5 M1 |5 i: a" u% t
3 [6 z6 W2 w( b( U2 Z) O8 H4 ]& k
# open the maps and mem files of the process5 o4 Q3 _( m: d: e6 `& }
maps_filename = "/proc/{}/maps".format(pid). L% v+ n' x2 k' V) f
print("
  • maps: {}".format(maps_filename)); B& ~" \8 G9 G- c# ~, b+ |
    mem_filename = "/proc/{}/mem".format(pid)
    8 _9 F" E, U+ {& b* s5 ]print("
  • mem: {}".format(mem_filename))7 k) q+ V9 F6 U( f" \, T0 {
    5 D$ E7 X* h1 V5 Y. M7 Y3 p" a. I
    # try opening the maps file
    7 A0 ?  D' H) y% s9 f7 [try:( U7 V3 ?0 s. v2 J' X
        maps_file = open('/proc/{}/maps'.format(pid), 'r')
    " O) l) {6 y% Dexcept IOError as e:
    3 ]5 b, E- L& s2 i    print("[ERROR] Can not open file {}:".format(maps_filename))
    * C8 o- B# q7 J* f- L8 n" _    print("        I/O error({}): {}".format(e.errno, e.strerror))
    " G: N3 K! o! c+ g6 |2 r. W    sys.exit(1)4 n# p! I- h8 M8 P, p' U
      }2 q: c5 u4 g  B( R
    for line in maps_file:
    ' s  q7 ^* U1 j/ j$ W) |6 q4 f    sline = line.split(' ')
    0 f8 M- P+ h; G1 \& o- y    # check if we found the heap( m' b; {5 ?0 H* c* l" U& h/ k
        if sline[-1][:-1] != "[heap]":
    & S& S1 Q0 m& |, U3 T        continue
    7 s/ I7 k5 Q$ s1 Y    print("
  • Found [heap]:")
    7 R% D0 b8 ?6 q. w+ t0 f4 q. Q+ V9 e6 d  ^& K, ?
        # parse line
    6 W0 m) H; a; A1 L2 `2 g    addr = sline[0]
    + a; v1 }6 l( u) ?% m    perm = sline[1]
    5 |0 A6 r0 i) q    offset = sline[2]7 P  a0 S' R8 o  b: N+ `
        device = sline[3]
    7 f4 i% T( \3 K7 C+ y    inode = sline[4]
    , y' w( J$ I. x2 j0 f1 [    pathname = sline[-1][:-1]
    2 a1 W9 ]) B& D% B8 B6 G0 \! K    print("\tpathname = {}".format(pathname))# o3 \8 u$ E$ Y8 _; T+ Y9 V0 ^
        print("\taddresses = {}".format(addr))
    5 ^, i5 m1 L# b) W1 l2 i$ ^& I    print("\tpermisions = {}".format(perm))
    ) f6 Z* Q$ r# [" h. k, T4 z" F    print("\toffset = {}".format(offset))# s6 Z0 D7 c. u4 O
        print("\tinode = {}".format(inode))
    5 n8 C% a$ j4 B6 N4 ]/ K* L+ ?: E. m5 T+ k* w9 j
        # check if there is read and write permission
    # I$ G6 i1 ?2 r) D8 @; E' W    if perm[0] != 'r' or perm[1] != 'w':
    7 u3 u1 e9 J8 z0 `( D- `- E1 K        print("
  • {} does not have read/write permission".format(pathname))
    ; T. S/ w, n# S7 `1 B        maps_file.close()4 c$ g! a8 z% f( }. |7 Z! h/ Q
            exit(0)
    # D! k2 P" H$ o
    $ t7 j# l+ s% ?$ ~0 i    # get start and end of the heap in the virtual memory9 j7 a# x2 g! x) D: e, s, f
        addr = addr.split("-")" T, i9 B2 R: y) c7 }
        if len(addr) != 2: # never trust anyone, not even your OS
    8 R7 M# g+ j4 x3 g& o5 r& M        print("
  • Wrong addr format")  l) Q- n. H1 k( ?! J0 h- w  ?
            maps_file.close()
    2 i1 t( x0 r& R/ J        exit(1)3 D7 H% T- Y8 X
        addr_start = int(addr[0], 16)- r7 h8 C( c( {, A( M  c, K
        addr_end = int(addr[1], 16)
    ; t6 Q$ {6 ?, F& [8 N' J    print("\tAddr start [{:x}] | end [{:x}]".format(addr_start, addr_end))
    $ i' U8 u# h7 {" j3 F
    6 y6 F" Q6 G( U- H1 m3 t) ^    # open and read mem
    - K' `' a% y$ I% x& e1 s! \4 x- d    try:
    " _$ w+ h: M1 A! i$ P+ z        mem_file = open(mem_filename, 'rb+')
    $ |  I( y+ w0 W0 y# o6 x    except IOError as e:
    ! P, |1 u$ X5 P; u1 Z/ D        print("[ERROR] Can not open file {}:".format(mem_filename))
    * u! B  i# a, y5 k: ^  a( R        print("        I/O error({}): {}".format(e.errno, e.strerror))
    3 {1 u. q% T) L6 H" ^" C0 a1 e* {        maps_file.close(). I7 X! X, n2 A; ?5 d
            exit(1)% d. Q5 }- A" I2 D

    : Q) E0 b& @9 \    # read heap  
    , h2 Y. u5 X. Y- l    mem_file.seek(addr_start)
    & p2 Y8 t! v: Z+ b+ B2 c+ Q3 S1 d    heap = mem_file.read(addr_end - addr_start)
    - j) ~# N5 \' q9 p+ `9 {/ f+ p, Y
    1 W/ H% e. G- e, h" e    # find string$ P0 T" x  f" y5 T: X/ r
        try:
    , u8 h! {2 d, ^3 w        i = heap.index(bytes(search_string, "ASCII"))9 q, T, x2 d( d
        except Exception:" \$ O9 y& b3 `4 W% n7 k& ^; C
            print("Can't find '{}'".format(search_string))
    7 f* s; J% v* t: H, f0 Y        maps_file.close(); S$ `" m2 P6 r% l  {) P) h1 S. |
            mem_file.close()
    & ]" ~, @/ i7 z/ b        exit(0)1 U8 I* J; m4 \$ n; H
        print("
  • Found '{}' at {:x}".format(search_string, i))
    3 g+ }- K4 X' r. H, G4 I) g0 p$ q$ x6 o5 I1 e  I
        # write the new string1 t/ n2 a3 X7 c
        print("
  • Writing '{}' at {:x}".format(write_string, addr_start + i))
    # z/ T# Y  P' o    mem_file.seek(addr_start + i)
    . ^/ L- s  z( Q' O    mem_file.write(bytes(write_string, "ASCII"))9 {0 E' ?* g  @8 i2 s7 Y7 c  T0 {

    8 d/ S. b4 X) N6 y    # close files  Y( U5 Z& p, k7 X& Z; ^
        maps_file.close()/ n) n4 g% h7 |% [' w" i5 e
        mem_file.close()! ?( h! d+ V; z+ h, z3 B* e: r6 ]

    ' }! c/ G0 N! D! ]+ o    # there is only one heap in our example% t4 D5 ~8 u# k7 @: C" V
        break
    ! Y% u) x; [2 t9 I. T* \- i

    运行这个Python脚本

    zjucad@zjucad-ONDA-H110-MINI-V3-01:~/wangzhiqiang$ sudo ./loop.py 2542 test_memory test_hello
  • maps: /proc/2542/maps
  • mem: /proc/2542/mem
  • Found [heap]:
    + j- g. ]; a; C: h3 E/ E+ t        pathname = [heap]9 V2 t; o0 D! i- o. a
            addresses = 021dc000-021fd000
      D6 Z0 W5 X4 F5 n& Y7 I! ?; T        permisions = rw-p2 o* m% J$ C* Q4 Z: l
            offset = 00000000- k4 l& C4 m+ P' e+ L0 _) L* M
            inode = 0
    - T* ?" l: d0 ]: E        Addr start [21dc000] | end [21fd000]
  • Found 'test_memory' at 10
  • Writing 'test_hello' at 21dc010* A2 A! ]0 ~) |# t2 ]/ J

    同时字符串输出的内容也已更改

    [633] test_memory (0x21dc010)
    5 r3 i4 ^- V: O8 ~" ]0 ]/ H[634] test_memory (0x21dc010)
    1 o9 T  m2 V, V4 ]0 u5 @0 W[635] test_memory (0x21dc010); N/ r* @# h( Q1 q& u8 J. P
    [636] test_memory (0x21dc010)) x9 A6 J$ I2 R: h+ _
    [637] test_memory (0x21dc010)
    ; I. ?# s& ^( g1 R. }: J[638] test_memory (0x21dc010)6 u% [8 T6 R; z8 |* s& s1 U
    [639] test_memory (0x21dc010)
    7 V. l) H) H4 G8 b/ a9 n[640] test_helloy (0x21dc010)
    ; x4 w4 d2 `1 l1 d[641] test_helloy (0x21dc010)
      v9 E, g+ N8 g9 y/ C: B[642] test_helloy (0x21dc010)
    6 ~3 I6 F" }1 F2 b8 x* h- y& u& B9 _[643] test_helloy (0x21dc010)7 l8 R2 C' {4 y' n  Q
    [644] test_helloy (0x21dc010)
      u$ ]7 S/ l" k$ j; {1 D[645] test_helloy (0x21dc010)
    * M, h4 z; E6 Z3 C8 V- _

    实验成功.

    通过实践画出虚拟内存空间分布图

    再列出内存空间分布图8 z% ?7 m, ^4 f
    微信图片_20201112205908.png

    基本上每个人或多或少都了解虚拟内存的空间分布,那如何验证它呢,下面会提到.

    堆栈空间

    首先验证栈空间的位置,我们都知道C中局部变量是存储在栈空间的,malloc分配的内存是存储在堆空间,所以可以通过打印出局部变量地址和malloc的返回内存地址的方式来验证堆栈空间在整个虚拟空间中的位置.

    #include <stdlib.h>6 ~+ b: ^! j! P
    #include <stdio.h>
    4 d9 f3 `' u& S& m" ^! z: B#include <string.h>  @4 O5 \  }" J. v8 N" N
    5 c6 D) X7 r' l: Z8 {0 \
    /**& ~/ f' i" z% ~7 B0 X+ ]/ r
    * main - print locations of various elements
    6 T$ i* O4 Q, f. T *
    ! @- Q& \( n9 P5 o* f- C * Return: EXIT_FAILURE if something failed. Otherwise EXIT_SUCCESS
    . P' Y4 F& I! W' E, b */
    , u# s- o: U2 u5 p" H: lint main(void)3 \6 @. x6 b1 ~0 |6 W% n5 ~- s
    {
    + b7 f' K/ j3 i  t  \- j0 m* [    int a;
    - ~* T3 c5 X* c+ W% f    void *p;; s% R. f3 o/ \) m
    # r: M4 `9 f+ G7 {
        printf("Address of a: %p\n", (void *)&a);
    " C  G$ V5 s- }    p = malloc(98);& B# E% V, @/ g  L
        if (p == NULL)
    / Z0 K, [9 q$ E8 v% P1 G& p  T    {# E' {: y% I' z
            fprintf(stderr, "Can't malloc\n");
    1 c4 }/ A6 O2 D! B9 _/ U2 f        return (EXIT_FAILURE);" a2 F; u# W7 h7 d. [
        }: h, }" o7 o! {+ s# p1 z
        printf("Allocated space in the heap: %p\n", p);/ i3 [$ _, q5 V- g8 \& f
        return (EXIT_SUCCESS);- K, L( W7 p8 l  R
    }3 J2 z. b4 G1 G& q: i
    编译运行:gcc -Wall -Wextra -pedantic -Werror main.c -o test; ./test
      {0 K+ ?$ q0 l7 `$ Q输出:$ T2 ^) R) v% e6 i1 P1 Z3 e6 h
    Address of a: 0x7ffedde9c7fc
    8 D8 O# z7 t8 gAllocated space in the heap: 0x55ca5b360670
    * f& `6 `3 U: s) w4 a

    通过结果可以看出堆地址空间在栈地址空间下面,整理如图:* X# y# ^5 u, |9 }% `
    微信图片_20201112205912.png

    可执行程序

    可执行程序也在虚拟内存中,可以通过打印main函数的地址,并与堆栈地址相比较,即可知道可执行程序地址相对于堆栈地址的分布.

    #include <stdlib.h>- L% F, ]  ]) S6 R% p( D% a+ o, H- F
    #include <stdio.h>: m& C8 l$ j' B" V2 U" b; u& o
    #include <string.h>" s9 I  j# B, N1 Q- l2 J- U

    2 [6 b) X( ~5 c/**
    ! i. B0 v8 f7 U8 t" ~( g * main - print locations of various elements
      ]4 [) ~0 s2 R5 s& D9 C& D *
    4 J. j3 ?: W4 N; W6 m * Return: EXIT_FAILURE if something failed. Otherwise EXIT_SUCCESS
    " c: P) l( `. E) @. B5 O */+ J. ~4 W$ h" k1 P
    int main(void)# ]! d! c& \) k* k, ~* r# l
    {& K$ A2 i1 P. {8 s- d% w2 d5 K3 _
        int a;
    ( ?/ N- D- C4 Z- B9 q$ i    void *p;+ q+ Y8 W. z; y8 |) Y4 u
      W$ [  E# Z* i' W- x6 M" H
        printf("Address of a: %p\n", (void *)&a);; N8 z$ b, Z9 z5 @9 y) S
        p = malloc(98);
    " o7 ~/ H; q& t    if (p == NULL). R; Q# E0 J; G
        {
    & e* c( Z% d9 I) }5 J        fprintf(stderr, "Can't malloc\n");' l1 G4 w( J0 v9 x) f. P2 [: {8 i
            return (EXIT_FAILURE);
    # o0 |! T/ w# D& e2 y/ Y! d8 l    }
    ' I" a# ?0 m$ ^# S% ^- i2 g    printf("Allocated space in the heap: %p\n", p);% j9 ]9 J: j& U* O/ m
        printf("Address of function main: %p\n", (void *)main);
    0 i9 V' k" }% w; E    return (EXIT_SUCCESS);
    - u3 v0 N: T: p3 Q0 w}
    ! y: H" t4 J; q1 T) j编译运行:gcc main.c -o test; ./test
    8 O6 ?! j- v1 h! w- }  u输出:
    9 p1 {$ W4 E. M' I9 WAddress of a: 0x7ffed846de2c
    $ Q7 B* A2 i. {Allocated space in the heap: 0x561b9ee8c670
    0 S" V' p/ u' [4 q( R2 `Address of function main: 0x561b9deb378a
    9 `/ Z9 f4 S% c

    由于main(0x561b9deb378a) < heap(0x561b9ee8c670) < (0x7ffed846de2c),可以画出分布图如下:

    微信图片_20201112205915.png virtual_memory_stack_heap_executable.png命令行参数和环境变量

    程序入口main函数可以携带参数:

    • 第一个参数(argc): 命令行参数的个数
    • 第二个参数(argv): 指向命令行参数数组的指针
    • 第三个参数(env): 指向环境变量数组的指针
      + Y' c4 y: {, k' m. b7 V8 b

    通过程序可以看见这些元素在虚拟内存中的位置:

    #include <stdlib.h>' g5 F) M/ m+ B5 E5 X. l
    #include <stdio.h>
    4 G2 \, }0 N  z9 [; f#include <string.h>2 @  K  @1 i: e8 b' z

    4 K# b0 v/ c" X- ~+ I/ z' P/**
    + D3 u+ B4 A+ m% U' ?+ ^" B * main - print locations of various elements
    ! r: \: |" n- X *
    ) M2 _$ d" C6 `- C1 K" b * Return: EXIT_FAILURE if something failed. Otherwise EXIT_SUCCESS& j, S3 c) r6 T+ P( p$ h
    */- F* a$ K+ d2 i3 }" h
    int main(int ac, char **av, char **env)
    ! G8 K, Y4 C) x! J6 Z$ |- k{
    ) ?/ V, j8 G' ~/ O        int a;
    1 t% V2 m# M( R  M0 g8 C( S$ P# `# R        void *p;  t2 U  I! o& z7 H  ]) B
            int i;* e+ y- h' b. ?8 w
    3 L* q+ \, k7 i# ~
            printf("Address of a: %p\n", (void *)&a);! t; F1 ^) h' g- w
            p = malloc(98);
    - Q7 @6 l) r( T        if (p == NULL)
      Q3 f) i9 s" Y4 h) l1 c5 k        {
    ; `( X' I7 ?1 i: |                fprintf(stderr, "Can't malloc\n");* I% m$ N3 {+ m- F6 e
                    return (EXIT_FAILURE);: D1 N3 l7 U- }4 H2 M6 p
            }
    0 I( m% j/ e& s* U8 F        printf("Allocated space in the heap: %p\n", p);0 _& V7 j2 x: }2 U7 K- r9 _0 m
            printf("Address of function main: %p\n", (void *)main);
    % n  D* p+ z* P$ T. V        printf("First bytes of the main function:\n\t");5 o' r" j6 K% ]& i
            for (i = 0; i < 15; i++). L9 O1 z9 U! U- H) v2 f# b. J
            {* c( A" z7 Q% G! J" j! O5 R
                    printf("%02x ", ((unsigned char *)main));$ Q! j, T' `9 q
            }
    ' S6 n7 z( Q) e; s: p; K        printf("\n");# T; p3 n' a) M0 M% ^& q
            printf("Address of the array of arguments: %p\n", (void *)av);
    . w4 E5 ^" J0 o1 s3 y- H        printf("Addresses of the arguments:\n\t");
    # Y5 U4 {, a( i* V# U        for (i = 0; i < ac; i++)
    4 m% @! R3 C* \; z/ X        {
    . d1 x+ o" a, Y3 l# Q                printf("[%s]:%p ", av, av);# d1 f* e& Y! I! P2 [3 S
            }8 `5 m6 B5 {4 s$ z
            printf("\n");
    , [+ |$ K* E- C: ~        printf("Address of the array of environment variables: %p\n", (void *)env);0 x. y$ u& b1 e! D3 w) y
        printf("Address of the first environment variable: %p\n", (void *)(env[0]));
    $ F- `, j" z0 t% }3 x: `        return (EXIT_SUCCESS);
    8 S# b6 q; V3 s, {7 h! v4 f}# }# T( y) l8 K. K. f/ o
    编译运行:gcc main.c -o test; ./test nihao hello
    + ~: l2 G. r: `) i% h输出:
    . C0 l/ `6 S2 L( r4 `0 SAddress of a: 0x7ffcc154a748
    % p8 }- B6 F$ z$ }( OAllocated space in the heap: 0x559bd1bee670. s* D7 B7 d" D8 P
    Address of function main: 0x559bd09807ca
    * u5 r* T! _( W# N4 B! _First bytes of the main function:- Y9 B6 e0 x& ^8 C
            55 48 89 e5 48 83 ec 40 89 7d dc 48 89 75 d0
      ?* x0 k* C) n* AAddress of the array of arguments: 0x7ffcc154a848
    2 U: X4 A5 w7 X, E% KAddresses of the arguments:: s: e0 n5 X4 i. {! b6 l+ W
            [./test]:0x7ffcc154b94f [nihao]:0x7ffcc154b956 [hello]:0x7ffcc154b95c7 d; L& }/ d  d/ K' n7 _
    Address of the array of environment variables: 0x7ffcc154a868
    9 O7 h, x$ r1 q8 QAddress of the first environment variable: 0x7ffcc154b9621 `. _/ @: j. L  F# G* D

    结果如下:
    , Y0 r- [5 M# Y7 B" |1 {main(0x559bd09807ca) < heap(0x559bd1bee670) < stack(0x7ffcc154a748) < argv(0x7ffcc154a848) < env(0x7ffcc154a868) < arguments(0x7ffcc154b94f->0x7ffcc154b95c + 6)(6为hello+1('\0')) < env first(0x7ffcc154b962)
    7 C9 L% e$ J$ H6 S8 z' \, H可以看出所有的命令行参数都是相邻的,并且紧接着就是环境变量.

    argv和env数组地址是相邻的吗

    上例中argv有4个元素,命令行中有三个参数,还有一个NULL指向标记数组的末尾,每个指针是8字节,8*4=32, argv(0x7ffcc154a848) + 32(0x20) = env(0x7ffcc154a868),所以argv和env数组指针是相邻的.

    命令行参数地址紧随环境变量地址之后吗

    首先需要获取环境变量数组的大小,环境变量数组是以NULL结束的,所以可以遍历env数组,检查是否为NULL,获取数组大小,代码如下:

    #include <stdlib.h>
    ( g4 |7 o- }5 _#include <stdio.h>: M: u/ Y% _6 Z; p
    #include <string.h>
    ( M" @5 A; J: d( q' T5 o# n6 j+ A# _; Z
    /**                                                                                                      6 t+ u% }4 X- ^
    * main - print locations of various elements                                                            
    0 G( P3 F; ]& b/ |  A5 u$ H *                                                                                                      
    - V. K+ u  o6 m& m% F9 _# D8 ~( m * Return: EXIT_FAILURE if something failed. Otherwise EXIT_SUCCESS                                      
    * N2 [# {* d* P) @  G6 ~1 |! Q* e7 T) X */" C, q: [& f. s1 D
    int main(int ac, char **av, char **env)
    & J! o9 Q+ q: @8 [1 ?{
    ! a$ [' V5 n  M% [" ^     int a;
    , z/ F4 i& F: A* z. a% n     void *p;
    ; @9 `1 F! V* c; z     int i;
    6 L' O$ M- n* C0 L     int size;% y, O) A. K: Y' B, V
    ! `! F4 ]& E' F1 b
         printf("Address of a: %p\n", (void *)&a);9 w8 l8 J0 U* Z
         p = malloc(98);
    : Z. a6 `, V. {5 H, a, ~. y8 D0 ?     if (p == NULL)
    , l' r4 v* u! R& ?7 I! K9 }     {$ M, t# S- Y, J9 R! {: r8 F( A! i
              fprintf(stderr, "Can't malloc\n");" B6 j1 l# {3 \4 X
              return (EXIT_FAILURE);
    6 }% \/ R1 }  f; I# @( v4 ~     }& `$ }3 p' ^1 A! A3 ^2 l) i% r
         printf("Allocated space in the heap: %p\n", p);6 J" m/ j, }) X6 A3 W, I& [" y
         printf("Address of function main: %p\n", (void *)main);
    4 i& N2 ^5 e/ N$ T" z8 `     printf("First bytes of the main function:\n\t");' U5 _0 [+ f# M/ w2 K9 X
         for (i = 0; i < 15; i++)' `, R' [, U8 Q6 r+ t7 t$ N
         {
    9 |; N9 M( v4 ~/ `$ v. h) `) F9 a; H          printf("%02x ", ((unsigned char *)main));- X  S4 e4 X! C  N0 G) x# v0 U' w
         }7 ]  l& a8 n5 n) {
         printf("\n");
    ! V- o: x- U9 J) X9 Y) x( W     printf("Address of the array of arguments: %p\n", (void *)av);
    ' H$ q9 `7 F4 z" m$ K/ Q     printf("Addresses of the arguments:\n\t");
    - _3 F! b/ }, k0 h/ R) ]3 i     for (i = 0; i < ac; i++)5 E1 W- C6 k8 ]" C
         {: e, ?( G  v7 v4 [5 N, l
              printf("[%s]:%p ", av, av);, T% t. M+ [8 E/ B4 F
         }
    - X6 _! I+ Y. z* Z9 C     printf("\n");
    2 \* L! b3 q2 j8 Z     printf("Address of the array of environment variables: %p\n", (void *)env);
    8 }% j+ X: ~* ^     printf("Address of the first environment variables:\n");
    $ e# g& n. M8 u: q+ z  W! o     for (i = 0; i < 3; i++)0 g# W2 w2 m/ k  N' p% D
         {' b" ^. o5 r+ {7 ?+ Z; q- W
              printf("\t[%p]:\"%s\"\n", env, env);7 [6 G* ]3 E3 X- }+ X
         }
    & ], U, P6 u+ ]; A     /* size of the env array */
    # A) h( `2 Y% N+ n1 S     i = 0;
      a+ }! i. h  g     while (env != NULL)* I* ]. Y" d+ n8 X2 g
         {: A$ c( t) y, N$ W% w: I
              i++;$ s9 B* P: l* P* w& r
         }( a. v" ~) Q  o3 v- ]# Q
         i++; /* the NULL pointer */1 m8 ^  g6 I* J  m
         size = i * sizeof(char *);
    - B$ q" Q. V$ n8 B8 y     printf("Size of the array env: %d elements -> %d bytes (0x%x)\n", i, size, size);
    : P6 q6 a5 ?3 o! M2 W! w     return (EXIT_SUCCESS);8 m4 b7 }+ m) a
    }
    , g5 Q1 S" x* s! x+ T" Y) X6 A& i  x* t
    编译运行:gcc main.c -o test; ./test nihao hello/ d; j0 s2 `" A
    输出:
    & z4 V+ D; @5 MAddress of a: 0x7ffd5ebadff4
    - e& ]8 e4 n$ M; f$ g: q) cAllocated space in the heap: 0x562ba4e13670, a- g- ]! M" G
    Address of function main: 0x562ba2f1881a
    4 r) o4 [( n0 F+ U9 b, ]7 JFirst bytes of the main function:
    " _7 J0 J" \7 g2 ^+ x        55 48 89 e5 48 83 ec 40 89 7d dc 48 89 75 d0
    ; ^( c" A" }& @; zAddress of the array of arguments: 0x7ffd5ebae0f8
    $ v9 ~/ b+ w  J6 q  M5 BAddresses of the arguments:5 W5 ]3 S* c  T" N8 A
            [./test]:0x7ffd5ebae94f [nihao]:0x7ffd5ebae956 [hello]:0x7ffd5ebae95c
    0 c+ Z6 C' o7 s6 G0 D1 N$ [Address of the array of environment variables: 0x7ffd5ebae118
    . a( y  q% G/ C5 A1 [, \Address of the first environment variables:. h. ]/ B/ h2 R! R( D. q* v
            [0x7ffd5ebae962]:"LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01r=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42w=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:*.arc=01;31:*.arj=01;31:*.taz=01;31:*.lha=01;31:*.lz4=01;31:*.lzh=01;31:*.lzma=01;31:*.tlz=01;31:*.txz=01;31:*.tzo=01;31:*.t7z=01;31:*.zip=01;31:*.z=01;31:*.Z=01;31:*.dz=01;31:*.gz=01;31:*.lrz=01;31:*.lz=01;31:*.lzo=01;31:*.xz=01;31:*.zst=01;31:*.tzst=01;31:*.bz2=01;31:*.bz=01;31:*.tbz=01;31:*.tbz2=01;31:*.tz=01;31:*.deb=01;31:*.rpm=01;31:*.jar=01;31:*.war=01;31:*.ear=01;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=01;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjpg=01;35:*.mjpeg=01;35:*.gif=01;35:*.bmp=01;35:*.pbm=01;35:*.pgm=01;35:*.ppm=01;35:*.tga=01;35:*.xbm=01;35:*.xpm=01;35:*.tif=01;35:*.tiff=01;35:*.png=01;35:*.svg=01;35:*.svgz=01;35:*.mng=01;35:*.pcx=01;35:*.mov=01;35:*.mpg=01;35:*.mpeg=01;35:*.m2v=01;35:*.mkv=01;35:*.webm=01;35:*.ogm=01;35:*.mp4=01;35:*.m4v=01;35:*.mp4v=01;35:*.vob=01;35:*.qt=01;35:*.nuv=01;35:*.wmv=01;35:*.asf=01;35:*.rm=01;35:*.rmvb=01;35:*.flc=01;35:*.avi=01;35:*.fli=01;35:*.flv=01;35:*.gl=01;35:*.dl=01;35:*.xcf=01;35:*.xwd=01;35:*.yuv=01;35:*.cgm=01;35:*.emf=01;35:*.ogv=01;35:*.ogx=01;35:*.aac=00;36:*.au=00;36:*.flac=00;36:*.m4a=00;36:*.mid=00;36:*.midi=00;36:*.mka=00;36:*.mp3=00;36:*.mpc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:"- J: j  v2 X# E; ^) z
            [0x7ffd5ebaef4e]:"HOSTNAME=3e8650948c0c"/ K1 ?7 T. Z, Y" v/ _3 L
            [0x7ffd5ebaef64]:"OLDPWD=/". ?  j0 x" ]6 x7 j; a) Q, M
    Size of the array env: 11 elements -> 88 bytes (0x58): j, Z( b. H0 k
    * Z; \- j0 i- M; C; @! M3 }
    运算结果如下:
    9 K) K/ l* R% a% Z8 q2 P  W: iroot@3e8650948c0c:/ubuntu# bc
    0 j) B1 k3 p6 mbc 1.07.11 [( C) }- p( F. y" c* L: a
    Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006, 2008, 2012-2017 Free Software Foundation, Inc.
    " @  {9 f2 }1 W% Y- uThis is free software with ABSOLUTELY NO WARRANTY.8 `1 m% p( A' r1 Z3 D  B/ r# D) S1 l
    For details type `warranty'.. B0 k+ J" \& _8 M3 _7 Q9 `2 v
    obase=16
    % R2 c, k! K9 j2 F# t& ?/ T+ Mibase=16/ p  j( t+ Q! c0 n8 Y
    58+7ffd5ebae118
    ! O* q) Y+ i9 |  ?. Y9 B(standard_in) 3: syntax error8 @" R1 d3 L+ A* ]- K' j( g
    58+7FFD5EBAE118
    . H0 x0 Y9 [* O7FFD5EBAE1707 i# k0 u+ V7 B
    quit3 R6 B; A- W# n" K' J+ R) k, _# n

    通过结果可知7FFD5EBAE170 != 0x7ffd5ebae94f,所以命令行参数地址不是紧随环境变量地址之后。  ]* w) j4 n7 j! K; [) M' K, ?


    ' W$ H4 ?* r" p* p6 R截至目前画出图表如下:: f* X: J: u& p6 h$ R
    微信图片_20201112205918.png
    栈内存真的向下增长吗

    可以通过调用函数来确认,如果真的是向下增长,那么调用函数的地址应该高于被调用函数地址, 代码如下:

    #include <stdlib.h>& b$ w/ V# E. V3 p7 a9 Q
    #include <stdio.h>
    ; f/ n0 t" L; c4 Q' Z/ K1 S#include <string.h>
    0 Z, c' ^& o9 O6 u2 @- L. a1 A: O  O8 J$ K0 [! M% Y: r
    void f(void)* x' N1 G# e4 ]7 B6 H/ {1 g
    {) h0 J- {0 v) N2 u
         int a;
    - f5 z) t( w0 U9 {9 s     int b;
    0 X/ e7 A9 H8 @9 P9 ?5 h3 d     int c;+ P8 N6 X" {5 k8 x% y
    , u: L2 x) z" ~, j/ W
         a = 98;8 j$ N& R% }8 \0 z8 |
         b = 1024;! j  h' d, a7 S9 o
         c = a * b;
    ' c% v. @; w/ z: F3 A' v     printf("[f] a = %d, b = %d, c = a * b = %d\n", a, b, c);1 A3 j8 }8 @' r! |3 M
         printf("[f] Adresses of a: %p, b = %p, c = %p\n", (void *)&a, (void *)&b, (void *)&c);
    ) z3 Z* g7 X- D}% V; A% @, Z3 M. S. F' G& B
    + [8 p3 w* z+ s) |; F
    int main(int ac, char **av, char **env)
    0 J# X3 ^! E9 H  c6 Y# P& o{, K& z1 f# q" T. V% ~
         int a;
      K  B( N5 u3 C! w# H- r4 {     void *p;
    / L& l/ l5 \2 X) A     int i;/ T: H; j4 W2 X7 Q( L) P: C
         int size;
    ) ^3 Z* z: g" B" }2 x6 A; a' |) O5 W0 ?$ z
         printf("Address of a: %p\n", (void *)&a);
    6 K. z( |0 a+ f# E7 k3 x     p = malloc(98);* Z3 y  R6 ~% D% K: {4 b! r& Z- Z4 v
         if (p == NULL)2 M# y3 G' W0 p3 N9 b% M7 @& \
         {9 I* R+ }0 B9 ?. W; K; o
              fprintf(stderr, "Can't malloc\n");
    - P" e( D5 M% B+ J4 ~8 ]          return (EXIT_FAILURE);
    8 ~  N# O  V9 Q+ d. ~! C: i     }9 Q1 c% Z$ I7 q; s6 F9 {/ {4 A2 V1 h& h
         printf("Allocated space in the heap: %p\n", p);* d# r5 ^/ s# o( \3 o& a% U
         printf("Address of function main: %p\n", (void *)main);; P7 `8 L. ?/ g3 N: i$ c9 [7 i$ ^; t
         f();
    5 @. k4 h( y0 P5 m! ^' Y     return (EXIT_SUCCESS);
    + P8 `  G, Z# a}& V( M! n* \3 K$ M- r
    编译运行:gcc main.c -o test; ./test, e  M) ~3 g4 Q- m: }# n  E& t
    输出:
    0 U# w+ Y2 y6 L1 q( n& GAddress of a: 0x7ffefc75083c) e) U1 D" n0 V# {3 ?- k
    Allocated space in the heap: 0x564d46318670
    ' V4 R9 C0 I  k; xAddress of function main: 0x564d45b9880e5 x! t+ t4 ^" W" j/ b9 d$ r. r
    [f] a = 98, b = 1024, c = a * b = 100352
    % r8 D" q8 }& o0 h0 ~" W[f] Adresses of a: 0x7ffefc7507ec, b = 0x7ffefc7507f0, c = 0x7ffefc7507f4
    / ~2 a( E1 e0 _* T! w* N+ x

    结果可知: f{a} 0x7ffefc7507ec < main{a} 0x7ffefc75083c' K+ a" A4 |! m0 E
    可画图如下:: a7 G( O+ e& I* w8 r0 Z
    微信图片_20201112205921.png

    其实也可以写一个简单的代码,通过查看/proc文件系统中map内容来查看内存分布,这里就不举例啦.

    堆内存(malloc)malloc

    malloc是常用的动态分配内存的函数,malloc申请的内存分配在堆中,注意malloc是glibc函数,不是系统调用.

    ' ^; ]4 N, u. ?4 A
    man malloc:

    [...] allocate dynamic memory[...]
    " d" v+ d- V$ x8 `1 W+ r! @  Vvoid *malloc(size_t size);2 K/ s; N' w9 Z/ q# P+ X4 [( ~
    [...]
    2 y! k- ?( }- T9 BThe malloc() function allocates size bytes and returns a pointer to the allocated memory." d; G9 L( |) O+ O* V- X7 |$ G
    不调用malloc,就不会有堆空间[heap]

    看一段不调用malloc的代码

    #include <stdlib.h>& h" f/ N/ W, r6 g6 P: ~# C
    #include <stdio.h>
    3 z* U8 f* `; W6 q4 |/ a, l6 R4 |+ N! L2 O3 }
    /**& B9 u. P( N$ t& V7 l2 Z8 v+ i
    * main - do nothing
    1 ?. f8 f  Q0 s) S, R ** O' Y' O! {4 h. C% {6 \( z- t. ~
    * Return: EXIT_FAILURE if something failed. Otherwise EXIT_SUCCESS* m  o1 ^8 R! c% ^
    */
    5 e* x- d  g' C2 B5 A( r/ P' vint main(void). ?# D& O+ L9 G1 T9 b3 x2 s7 l) F
    {- ~! ?7 X, r1 U2 \4 z
        getchar();
    * x2 A' b- K. p3 I    return (EXIT_SUCCESS);7 g, [% {2 v' O' a# y8 l* m
    }  d* N2 [* t# Y5 A
    编译运行:gcc test.c -o 2; ./2
      }- L  o" {! ?step 1 : ps aux | grep \ \./2$0 E1 o) w( D& o, }  S
    输出:6 P' S) S/ [, k; t* b2 v2 l" F8 l
    zjucad    3023  0.0  0.0   4352   788 pts/3    S+   13:58   0:00 ./2, I; r, d8 P& x9 n$ Q
    step 2 : /proc/3023/maps3 l4 ~. P) B7 B& A( Y! e1 H
    输出:
    # o* b$ ~, T% e4 J00400000-00401000 r-xp 00000000 08:01 811723                             /home/zjucad/wangzhiqiang/2
    & a) Y" R. S5 X' o; f6 i2 c00600000-00601000 r--p 00000000 08:01 811723                             /home/zjucad/wangzhiqiang/20 X# X% g+ P1 @! f* D
    00601000-00602000 rw-p 00001000 08:01 811723                             /home/zjucad/wangzhiqiang/2
    ' g7 R$ i0 z  V/ S2 V7 v' v7 v+ ]2 I007a4000-007c5000 rw-p 00000000 00:00 0                                  [heap]( d4 O* q4 i4 f& J, v, r
    7f954ca02000-7f954cbc2000 r-xp 00000000 08:01 8661324                    /lib/x86_64-linux-gnu/libc-2.23.so
    % P: H6 o/ D$ z8 c% p7f954cbc2000-7f954cdc2000 ---p 001c0000 08:01 8661324                    /lib/x86_64-linux-gnu/libc-2.23.so
    / f; `7 r4 P% e7 [% h7f954cdc2000-7f954cdc6000 r--p 001c0000 08:01 8661324                    /lib/x86_64-linux-gnu/libc-2.23.so8 [8 K6 g/ q: F3 Y. @2 S+ O
    7f954cdc6000-7f954cdc8000 rw-p 001c4000 08:01 8661324                    /lib/x86_64-linux-gnu/libc-2.23.so% Z1 {" d, R5 D; _) b% A
    7f954cdc8000-7f954cdcc000 rw-p 00000000 00:00 0
    - O4 a/ w: g) T) T7f954cdcc000-7f954cdf2000 r-xp 00000000 08:01 8661310                    /lib/x86_64-linux-gnu/ld-2.23.so
    4 b3 K- n; D1 h4 v8 m- y; ^7f954cfd2000-7f954cfd5000 rw-p 00000000 00:00 0
    + G0 c) w( {( a: ]6 s$ X4 N7f954cff1000-7f954cff2000 r--p 00025000 08:01 8661310                    /lib/x86_64-linux-gnu/ld-2.23.so
    0 k7 i8 [9 |4 c2 l. `7f954cff2000-7f954cff3000 rw-p 00026000 08:01 8661310                    /lib/x86_64-linux-gnu/ld-2.23.so4 E# @5 \1 W2 p4 }! \
    7f954cff3000-7f954cff4000 rw-p 00000000 00:00 0- o) g+ O* \6 }7 W9 r2 @' N
    7ffed68a1000-7ffed68c2000 rw-p 00000000 00:00 0                          [stack], c- r/ i+ Y+ q4 z2 _# [
    7ffed690e000-7ffed6911000 r--p 00000000 00:00 0                          [vvar]9 G2 Y2 G* g, K) ]5 P5 W& \
    7ffed6911000-7ffed6913000 r-xp 00000000 00:00 0                          [vdso]
    0 `( r/ i# ~* y0 y. }" cffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
    ( f8 z* C% I$ n9 B" l6 Z! m

    可以看到,如果不调用malloc,maps中就没有[heap]

    下面运行一个带有malloc的程序

    #include <stdio.h>$ r! ]# ^3 j1 X  ~9 e# W6 p0 _
    #include <stdlib.h>! [( Y5 s7 I/ l; O
    & M9 q! G1 }) @; d% D+ H
    /**
    * M0 |5 j0 e. G% ^1 B * main - prints the malloc returned address7 N5 {# l6 v" E6 C( ^
    *2 s  F: D. \# C8 g& x4 e+ v, X
    * Return: EXIT_FAILURE if something failed. Otherwise EXIT_SUCCESS: |/ O4 f9 W) i  I% ~
    */  u9 q, @4 }# ]3 x& i1 I
    int main(void)
    3 B5 D1 N3 T1 p  J: c, v{* l) P/ z, X) `: k. l* B
        void *p;
    ( H! {2 x& d  N! l
    & a% T1 d/ @+ F' C6 {5 t    p = malloc(1);. D  Q8 B4 C# E5 S5 j
        printf("%p\n", p);
    0 g. J- D  f/ w1 b' w- K5 w* j0 O    getchar();
      p# E% c9 g4 t3 u# p! C    return (EXIT_SUCCESS);& z2 O" I' r; p: `
    }
    ) @% T4 {" b8 Z' {) g% u编译运行:gcc test.c -o 3; ./3
    1 r+ }! G  Q0 \2 i( Q2 O输出:0xcc7010
    ) h1 H7 _$ ^+ {0 i+ a验证步骤及输出:. d  ?& T3 w2 J8 e: v
    zjucad@zjucad-ONDA-H110-MINI-V3-01:~/wangzhiqiang$ ps aux | grep \ \./3$4 |9 E2 d) I! R; X
    zjucad    3113  0.0  0.0   4352   644 pts/3    S+   14:06   0:00 ./3, ]: a6 C  F( |( g* H
    zjucad@zjucad-ONDA-H110-MINI-V3-01:~/wangzhiqiang$ cat /proc/3113/maps
    / F: E0 @* P, ?00400000-00401000 r-xp 00000000 08:01 811726                             /home/zjucad/wangzhiqiang/3+ e2 V# f) c6 x$ |6 Y$ @
    00600000-00601000 r--p 00000000 08:01 811726                             /home/zjucad/wangzhiqiang/30 M2 d* h+ y7 x" z0 ]
    00601000-00602000 rw-p 00001000 08:01 811726                             /home/zjucad/wangzhiqiang/3- c8 l7 ^( ^( A3 _# l
    00cc7000-00ce8000 rw-p 00000000 00:00 0                                  [heap]
    % G$ e7 m: ?- [, J& v1 \; [+ b7fc7e9128000-7fc7e92e8000 r-xp 00000000 08:01 8661324                    /lib/x86_64-linux-gnu/libc-2.23.so
    ) M% g- G- N2 o7fc7e92e8000-7fc7e94e8000 ---p 001c0000 08:01 8661324                    /lib/x86_64-linux-gnu/libc-2.23.so
    3 ?; G  B& @* f5 v) C7fc7e94e8000-7fc7e94ec000 r--p 001c0000 08:01 8661324                    /lib/x86_64-linux-gnu/libc-2.23.so
      G: @# f6 V' e3 Y8 I, U# T7fc7e94ec000-7fc7e94ee000 rw-p 001c4000 08:01 8661324                    /lib/x86_64-linux-gnu/libc-2.23.so$ Q- ?- D1 H" {7 o/ I" H9 v( x; g
    7fc7e94ee000-7fc7e94f2000 rw-p 00000000 00:00 0
    1 R, `0 v5 z2 H! A" b: N7fc7e94f2000-7fc7e9518000 r-xp 00000000 08:01 8661310                    /lib/x86_64-linux-gnu/ld-2.23.so4 D1 A, q3 H6 j" u) n
    7fc7e96f8000-7fc7e96fb000 rw-p 00000000 00:00 0
    ) U: E' E3 r' Y2 Q  O- Z" v7fc7e9717000-7fc7e9718000 r--p 00025000 08:01 8661310                    /lib/x86_64-linux-gnu/ld-2.23.so5 ^6 Q( V4 ~7 S. Q, r; _: d+ ^& P
    7fc7e9718000-7fc7e9719000 rw-p 00026000 08:01 8661310                    /lib/x86_64-linux-gnu/ld-2.23.so
    9 Z& @/ ~7 m9 @3 g- _2 F+ l3 A7fc7e9719000-7fc7e971a000 rw-p 00000000 00:00 0
    $ E) h% E/ i4 k# X. Y7ffc91c18000-7ffc91c39000 rw-p 00000000 00:00 0                          [stack]) B& y# G9 u2 ~, Z1 d) s( A
    7ffc91d5f000-7ffc91d62000 r--p 00000000 00:00 0                          [vvar]
    , m; W5 |8 K& `7 }8 M7ffc91d62000-7ffc91d64000 r-xp 00000000 00:00 0                          [vdso]
    7 {& \- A# S5 ]4 H+ rffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]3 [0 v4 ~7 s( D

    程序中带有malloc,那maps中就有[heap]段,并且malloc返回的地址在heap的地址段中,但是返回的地址却不再heap的最开始地址上,相差了0x10字节,为什么呢?看下面:

    strace, brk, sbrk

    malloc不是系统调用,它是一个正常函数,它必须调用某些系统调用才可以操作堆内存,通过使用strace工具可以追踪进程的系统调用和信号,为了确认系统调用是malloc产生的,所以在malloc前后添加write系统调用方便定位问题。

    #include <stdio.h>
    % p7 {  n# {0 u$ Y0 t2 s1 V#include <stdlib.h>
    # m- {, W3 `" Q#include <unistd.h>
    . u# y7 \; F  t; t$ W0 i- P8 L" F  x7 V( J3 c; K: Y
    /**& c' Z' G/ r& m' [
    * main - let's find out which syscall malloc is using& H: S2 p5 E  p
    *
    $ x- Y8 k: A- b! L1 G. O+ ^ * Return: EXIT_FAILURE if something failed. Otherwise EXIT_SUCCESS3 H" d) B: e% n+ f  _1 r
    */+ R$ b& V* C$ M
    int main(void)
    ' p& h9 M* J" g. a{$ c0 [) s7 G' v; `- k/ f) r
        void *p;9 f! b2 X0 j% u7 e
    ) s; [) @( u4 s, n  E
        write(1, "BEFORE MALLOC\n", 14);
    ! T7 n# A/ m6 l' H    p = malloc(1);
    4 |1 Z+ R" R' C) u. K2 O    write(1, "AFTER MALLOC\n", 13);
    8 o6 F  }. p, G9 u+ ?! l& i1 Y    printf("%p\n", p);3 A0 l) y$ [8 w1 B" b+ a- v" h5 b
        getchar();
    + f1 x. c5 K* h, N; C. g    return (EXIT_SUCCESS);5 ?8 N  f& ~$ o$ J/ m
    }; @( q+ u7 O: }6 u5 |
    编译运行:gcc test.c -o 4
    % ]1 M: S9 a- s  D1 D. L# ?2 H' d& Wzjucad@zjucad-ONDA-H110-MINI-V3-01:~/wangzhiqiang$ strace ./45 u5 j! @+ B# O" n- l! n; S0 v4 _
    execve("./4", ["./4"], [/* 34 vars */]) = 0/ c) ~8 S7 t! T! @! E9 M
    brk(NULL)                               = 0x781000
    # ?, l7 j4 R% paccess("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
    - d4 G+ N  \. t' P6 h& c% }access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)% I  H8 S; }5 x- t! n& t9 [
    open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3  S: I6 s- ]& \: R0 m" P) [
    fstat(3, {st_mode=S_IFREG|0644, st_size=111450, ...}) = 0( E3 B, a. v; J: n* ?5 T
    mmap(NULL, 111450, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f37720fa000! r# w: H, B- d7 @+ |
    close(3)                                = 0
    4 e0 e# t1 {% ]; z4 U+ Qaccess("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)/ R, v* N3 b2 o
    open("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
      N2 `+ \9 l  F7 i, v' X3 N8 Iread(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0P\t\2\0\0\0\0\0"..., 832) = 832
    ; Z9 e2 z9 M' `/ K& b# Tfstat(3, {st_mode=S_IFREG|0755, st_size=1868984, ...}) = 0( h' O. o$ A$ `8 s8 z: Y2 b8 ~) e
    mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f37720f9000# y- t! ^& Y8 W( s9 w4 m
    mmap(NULL, 3971488, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f3771b27000
    4 R5 M/ }  Q2 jmprotect(0x7f3771ce7000, 2097152, PROT_NONE) = 0
    / ^6 F, k0 [9 G$ tmmap(0x7f3771ee7000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c0000) = 0x7f3771ee7000
    6 }0 {  t9 f' bmmap(0x7f3771eed000, 14752, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f3771eed000
      U+ M8 n# ]' I" R! `close(3)                                = 0
    / E  w0 \$ T3 |" I7 W( e8 [1 N+ fmmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f37720f8000
    ' \( {' O# W% `. u5 i6 ^mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f37720f7000
    : M% Z: z. p  Carch_prctl(ARCH_SET_FS, 0x7f37720f8700) = 0: h/ d: `+ u1 ?' w
    mprotect(0x7f3771ee7000, 16384, PROT_READ) = 0
    - V3 E3 t4 n3 _5 F/ zmprotect(0x600000, 4096, PROT_READ)     = 0
    0 X3 k4 R- y* C$ `7 Omprotect(0x7f3772116000, 4096, PROT_READ) = 0( Y. W  B, J" m" m, B2 o
    munmap(0x7f37720fa000, 111450)          = 0
    2 c- h5 A! E, C3 R$ b! L& W1 L& Y3 gwrite(1, "BEFORE MALLOC\n", 14BEFORE MALLOC
    3 [# z1 Y/ w) f/ R)         = 14
    : G- M+ U  E' B3 \% [( nbrk(NULL)                               = 0x7810009 Z, L2 ?5 K6 O7 O: |& U. ]# r
    brk(0x7a2000)                           = 0x7a2000
    ) i% G1 F4 {- U' I# t9 W3 qwrite(1, "AFTER MALLOC\n", 13AFTER MALLOC3 |+ B* m" z2 q8 Y6 H
    )          = 13- b! x8 ~# S6 c( E( w$ U0 Y
    fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 3), ...}) = 0
    : q7 v2 r& L9 `; [. f8 O2 s* uwrite(1, "0x781010\n", 90x7810106 U8 G* s; l8 S/ {4 M
    )               = 9
    ) X  v) Q; a& K7 Q2 ~fstat(0, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 3), ...}) = 0& @, l! n2 U5 o6 ^) S# o

    最后几行的输出可知,malloc主要调用brk系统调用来操作堆内存.

    man brk& ^+ _0 m& Y- ]) v2 O+ k
    ...
    # \# c+ u9 P$ ~1 b7 |8 y4 e       int brk(void *addr);& B1 A3 ?0 o! |! \& A" J& H* o; m5 i3 M
           void *sbrk(intptr_t increment);
    . T8 {+ {1 W. H- _...* g. _, ]3 @( W- R
    DESCRIPTION3 Y) h% w, t; u+ A
           brk() and sbrk() change the location of the program  break,  which  defines/ x! U; J) \, @9 o* T, B7 a4 G) U
           the end of the process's data segment (i.e., the program break is the first' f' h" Y* _# W4 v, Z7 T( k* h
           location after the end of the uninitialized data segment).  Increasing  the
    6 U1 j: ^/ _6 w5 E5 A9 v# ?1 m       program  break has the effect of allocating memory to the process; decreas‐+ I8 z1 ]2 x0 h2 C  J; M
           ing the break deallocates memory.
    8 r- b, _" F& {; F, |: [/ h( G. F! X* {3 g, N
           brk() sets the end of the data segment to the value specified by addr, when/ _: P6 l" ^% Y8 A* Q* S
           that  value  is  reasonable,  the system has enough memory, and the process: F8 o: u" D# |' m5 C$ Q
           does not exceed its maximum data size (see setrlimit(2)).4 C7 ^! h& v5 c- {- N

    & p" D3 o0 L$ u1 u( S       sbrk() increments the program's data space  by  increment  bytes.   Calling
    2 A' t1 P9 u8 }8 L$ Y       sbrk()  with  an increment of 0 can be used to find the current location of, G9 L# N5 L7 K( M+ R+ K7 f: z
           the program break.9 l' L( R9 K4 G* I/ C8 j$ U- m

    程序中断是虚拟内存中程序数据段结束后的第一个位置的地址,malloc通过调用brk或者sbrk,增加程序中断的值就可以创建新空间来动态分配内存,首次调用brk会返回当前程序中断的地址,第二次调用brk也会返回程序中断的地址,可以发现第二次brk返回地址大于第一次brk返回地址,brk就是通过增加程序中断地址的方式来分配内存,可以看出现在的堆地址范围是0x781000-0x7a2000,通过cat /proc/[pid]/maps也可以验证,此处就不贴上实际验证的结果啦。

    多次malloc

    如果多次malloc会出现什么现象呢,代码如下:

    #include <stdio.h>* z  ^' l' L5 u8 p* ^
    #include <stdlib.h>( v$ a0 g! l. x
    #include <unistd.h>
    + h* `0 v+ M9 X9 Y$ X7 w" E& P7 H, P5 Z! ^
    /**9 T0 ]0 f& A: B3 q  \% q. L
    * main - many calls to malloc$ t( O! N) \7 o3 i  ], E; N+ d
    *
    8 c0 I" j  F9 Z) S/ ~+ i: P * Return: EXIT_FAILURE if something failed. Otherwise EXIT_SUCCESS0 F& a- L4 s' Z3 ~; h0 n
    */
    * D+ ^0 H5 H7 N3 Q) e8 T$ Tint main(void)
    ; A3 {7 N; E: U{
    / A% h3 \$ \1 y0 [& J% F% H- x    void *p;: h3 k1 C8 C0 C6 g6 U
    ! {& w$ k, S4 {' j4 s
        write(1, "BEFORE MALLOC #0\n", 17);
    . t7 a3 V- j2 P' q9 w# r- n    p = malloc(1024);: d! l) l2 n5 i7 O. T) l% F+ b
        write(1, "AFTER MALLOC #0\n", 16);- e' l, X) c" S7 w; u3 R8 l
        printf("%p\n", p);
    ; o1 f; {' ?- W7 A, r' M5 q1 Q% o2 R6 H/ ?* F
        write(1, "BEFORE MALLOC #1\n", 17);
    # ]- ~* a/ G; [* y& v" q" @* C    p = malloc(1024);5 P3 l% ]9 g6 L/ c, ~! D4 G3 B
        write(1, "AFTER MALLOC #1\n", 16);& Q* t6 I) h( V. Z
        printf("%p\n", p);
    ' v% u" k3 Y$ @# D
    ) N+ u( Z# ^' [    write(1, "BEFORE MALLOC #2\n", 17);5 `+ x8 p+ V" L( J& a
        p = malloc(1024);
    4 K. y/ y9 K% D    write(1, "AFTER MALLOC #2\n", 16);
    3 A+ A& u3 K  `9 B8 z0 v; `    printf("%p\n", p);0 T+ V$ P# N3 z3 b8 M% W4 A

    + q, u; W- o+ W2 g    write(1, "BEFORE MALLOC #3\n", 17);
    ) U$ w, U3 d1 b& F$ |! F4 W    p = malloc(1024);, R8 ~1 `. J3 I: B: u
        write(1, "AFTER MALLOC #3\n", 16);* i( e/ |4 a" X8 E
        printf("%p\n", p);
    & o) ?3 ?+ w! v3 F
    4 U3 `9 ?& E* a- v. r9 y    getchar();% u& m1 C4 V/ H
        return (EXIT_SUCCESS);
    2 H( G; t" N: Z3 K" u3 e}+ E8 T8 U5 ~0 \. Q
    编译运行:gcc test.c -o 5; strace ./52 T, A2 h, m! B
    摘要输出结果如下:6 i' l2 [) y# `9 e! S3 c; H
    write(1, "BEFORE MALLOC #0\n", 17BEFORE MALLOC #0. g/ O2 l! g" I6 d  w" _
    )      = 17( z* I! h2 C# j. ^
    brk(NULL)                               = 0x561605c7a000
    % _1 A) i3 S' v1 `0 bbrk(0x561605c9b000)                     = 0x561605c9b000( K" E$ \8 R1 M1 E1 I$ y
    write(1, "AFTER MALLOC #0\n", 16AFTER MALLOC #0
    - I8 N! b, |9 K2 G)       = 16) B  R' R3 y& |% e! @+ n
    fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
    % W* D" N3 [/ n9 D1 U( ~write(1, "0x561605c7a260\n", 150x561605c7a260
    5 u) y% P8 m. O% O; @: M& I8 T! z)        = 15
    1 q! R2 G. x  o& v/ c$ e# a5 \( Dwrite(1, "BEFORE MALLOC #1\n", 17BEFORE MALLOC #1; v1 u$ ^3 u. T3 L, o. {
    )      = 17
    ) ?6 u* D8 S$ f7 C9 _write(1, "AFTER MALLOC #1\n", 16AFTER MALLOC #1% O0 M. c1 d; w
    )       = 16, j6 N0 E4 s5 g. L6 |
    write(1, "0x561605c7aa80\n", 150x561605c7aa801 w0 [. w/ L: S4 Q% r- P2 [
    )        = 15
    ! D% [; Q1 Y6 e- @' p, B" jwrite(1, "BEFORE MALLOC #2\n", 17BEFORE MALLOC #22 C* M# A+ Z1 R# m0 B# s
    )      = 17
    $ E+ O4 r+ `+ Wwrite(1, "AFTER MALLOC #2\n", 16AFTER MALLOC #2+ {7 h. I( W+ h& J
    )       = 16
    , \* B9 n; M# X/ D* S) e: Pwrite(1, "0x561605c7ae90\n", 150x561605c7ae906 r4 ^8 K5 G7 e" a( @
    )        = 15
    , e6 ?6 I; P! D3 Z  E. U( M! mwrite(1, "BEFORE MALLOC #3\n", 17BEFORE MALLOC #3
    & w" n/ _% ^, H- f- ^4 ^$ M)      = 17- Q/ W' f% b8 h5 A+ K
    write(1, "AFTER MALLOC #3\n", 16AFTER MALLOC #32 C% W5 A4 a/ v9 X) y+ `  |
    )       = 16
    . E5 H/ B$ Y& V6 G4 pwrite(1, "0x561605c7b2a0\n", 150x561605c7b2a0
    % Q, k7 g3 @* ^5 e/ ~)        = 15
    1 }% l+ B) L2 l5 Y1 D4 ~fstat(0, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
    ' v0 d! P; k8 b7 ~

    可以发现并不是每次调用malloc都会触发brk系统调用,首次调用malloc,内部会通过brk系统调用更改程序中断地址,分配出一大块内存空间,后续再调用malloc,malloc内部会优先使用之前分配出来的内存空间,直到内部内存空间已经不够再次分配给外部时才会再次触发brk系统调用.

    0x10 那丢失的16字节是什么

    上面分析可以看见程序第一次调用malloc返回的地址并不是heap段的首地址,而是相差了0x10个字节,那这16个字节究竟是什么,可以通过程序打印出这前16个字节的内容.

    编译运行:gcc test.c -o test;./test. O$ X0 o( [, p6 M+ V
    输出:$ f3 ?, D# _0 F4 d; J3 \7 d5 D
    0x5589436ce260% Z8 ~0 X7 b+ ]% R4 F& O* X
    bytes at 0x5589436ce250:: {; O% h7 n1 [; T3 F$ o7 r$ L
    00 00 00 00 00 00 00 00 11 04 00 00 00 00 00 00
    # @9 M0 L  W9 }0x5589436cea80, r" P1 r( c6 @8 }4 P6 r
    bytes at 0x5589436cea70:
    + y* {, a. I! l# L$ t, a00 00 00 00 00 00 00 00 11 08 00 00 00 00 00 00, h% ]# j1 ]2 d; {) w
    0x5589436cf290
    ; b- @: y2 _. x& W% M( L' ubytes at 0x5589436cf280:0 R/ T  n$ ^' n* B
    00 00 00 00 00 00 00 00 11 0c 00 00 00 00 00 00- D" T7 R( d4 X  z0 B
    0x5589436cfea0
    5 R5 {# _0 k( k  ]! x5 kbytes at 0x5589436cfe90:, |% A& `% ], @- G3 y# k8 ~1 t- j5 }
    00 00 00 00 00 00 00 00 11 10 00 00 00 00 00 00* `- m7 T6 }) k6 B9 T
    0x5589436d0eb0
    * x) `' {/ C  o, p- F' A( Gbytes at 0x5589436d0ea0:
    5 ?6 ]' x. c2 {* E6 }00 00 00 00 00 00 00 00 11 14 00 00 00 00 00 005 j" a) U/ b: \) z& [
    0x5589436d22c0
    5 }+ L  E' I5 g, Z+ Z$ d, U; S5 V* dbytes at 0x5589436d22b0:9 [0 K6 Q9 [3 s. P, H4 t- q
    00 00 00 00 00 00 00 00 11 18 00 00 00 00 00 00
    " c- t" }( Z- t* M0x5589436d3ad0( z! M# L2 d9 @3 U
    bytes at 0x5589436d3ac0:3 m9 J2 o- z7 A$ Q9 _' s# a
    00 00 00 00 00 00 00 00 11 1c 00 00 00 00 00 008 p0 r* [: K9 W: T* @5 {
    0x5589436d56e0
    , g$ p4 {* e; ?8 |8 Ibytes at 0x5589436d56d0:" ?1 ?, C7 L, J8 g- P. ]; X
    00 00 00 00 00 00 00 00 11 20 00 00 00 00 00 00& y2 z6 z% Y& f5 F  n1 F6 O
    0x5589436d76f0
    1 x, O& p7 o: z5 u: pbytes at 0x5589436d76e0:5 a+ I- Z; Q! n; C7 X
    00 00 00 00 00 00 00 00 11 24 00 00 00 00 00 00. X, x4 ?8 \; B' [
    0x5589436d9b00
    4 c. T1 p. N8 U4 T9 ^  [bytes at 0x5589436d9af0:
    . p4 Z7 k) b# T: H  h2 r' l00 00 00 00 00 00 00 00 11 28 00 00 00 00 00 00
      i% J, @4 J( [2 [5 W

    可以看出规律:这16个字节相当于malloc出来的地址的头,包含一些信息,目前可以看出它包括已经分配的地址空间的大小,第一次malloc申请了0x400(1024)字节,可以发现11 04 00 00 00 00 00 00大于0x400(1024),这8个字节表示数字 0x 00 00 00 00 00 00 04 11 = 0x400(1024) + 0x10(头的大小16) + 1(后面会说明它的含义),可以发现每次调用malloc,这前8个字节代表的含义都是malloc字节数+16+1.


    % f& h- v, P' R" B+ w7 g+ S- U可以猜测,malloc内部会把这前16个字节强转成某种数据结构,数据结构包含某些信息,最主要的是已经分配的字节数,尽管我们不了解具体结构,但是也可以通过代码操作这16个字节验证我们上面总结的规律是否正确,注意代码中不调用free释放内存.

    #include <stdio.h>
    7 z5 p$ N. _( T2 y' k6 i#include <stdlib.h>
    , i3 j8 a7 R% x2 t7 J$ h1 a#include <unistd.h>
    9 n, b: u" B1 |/ O+ z/ t
    : `9 s, L& T: m% x- O" ~8 a$ i& c/**                                                                9 Z1 W( [$ @1 ^
    * pmem - print mem                                                . u% s: U+ g  d' M, P
    * @p: memory address to start printing from                       5 o6 D4 T) k' f# F' i1 g, \
    * @bytes: number of bytes to print                                
    8 ?/ ?! K) F* a3 i9 x *                                                                 
    ( a* F- ~# H7 n* F- L * Return: nothing1 k9 m6 k& X9 G4 v
    */3 L" Q. V( t( g: s* U
    void pmem(void *p, unsigned int bytes). V8 x" n7 g: o  y, c% m7 |( P
    {# P+ s+ z* o" R" V$ d
        unsigned char *ptr;! l/ F1 v. x( A, b. S. o# E. g
        unsigned int i;) x, t1 T5 e0 b$ C2 v

    % P6 }/ x2 n& u4 r$ A- w    ptr = (unsigned char *)p;9 L1 [! b* b  b/ ]9 h' T
        for (i = 0; i < bytes; i++)
    3 B0 D, V4 |: M* J# m    {
    7 I+ u& N# C) T* x/ Z# e$ b$ `- ?        if (i != 0)
    # X) b5 w7 |$ H        {: z# N% J; J, W  n
                printf(" ");; `: B* D0 y% ^& w2 V/ A6 G
            }. _! x9 N% j- U8 l, m
            printf("%02x", *(ptr + i));
    ( i8 w; R0 c& c( G7 r5 E* m- J    }
    2 P5 d" r1 ?7 R$ ~, `% k    printf("\n");9 N5 g/ G3 v0 b
    }/ N5 M* R2 h4 }4 m
    - W, S9 {) V$ \! z5 n6 K! k
    /**
    ' _" x: }2 Y4 b, _ * main - confirm the source code
    + Y2 h1 c! S( o$ x0 { *
    ' Y5 U: B1 O, a1 } * Return: EXIT_FAILURE if something failed. Otherwise EXIT_SUCCESS
    * q: {) p+ w2 |0 q, o */
    ' j6 q5 @6 l6 ^5 \( cint main(void)- o8 m, C1 b5 w. y" T. b
    {1 n9 G, `0 I9 J) L
        void *p;
    3 o! J7 }* z; X. G    int i;- M& o  i& G0 t" F
        size_t size_of_the_chunk;% y. L1 Y/ p, O: V( M
        size_t size_of_the_previous_chunk;1 z. M) u- A6 X! H7 Q8 Z% t$ y1 J
        void *chunks[10];# ]6 z0 Y0 c/ }1 c
    % X' B5 V) B% x" u  u
        for (i = 0; i < 10; i++)4 g* \! c& z/ q2 A: M, p  i
        {! J  X* X7 b" P" R5 l9 u% [7 u& s
            p = malloc(1024 * (i + 1));
    - C& k  n5 k& H* c        chunks = (void *)((char *)p - 0x10);
    1 C: [" x6 [8 [2 A! |  C        printf("%p\n", p);+ b+ |& ~, E* s- D4 C1 O" O7 Z
        }% _4 ^$ Y$ A- m1 o+ y
        free((char *)(chunks[3]) + 0x10);9 y/ c: o9 J3 f: b- h+ ]# N) a6 @
        free((char *)(chunks[7]) + 0x10);
    2 z8 J! T7 F; r9 |# v# h$ P5 j    for (i = 0; i < 10; i++)
    ! M& Q! u6 |: P" w9 |/ O3 v    {
    1 F3 s3 J' p# @: g  d! p1 b1 L        p = chunks;
    4 H  c/ ^: M. y        printf("chunks[%d]: ", i);
    : k8 H* `# k0 Q: K9 k  T& O/ n, p        pmem(p, 0x10);
    / ~  ^) O" v3 e6 a; W; P        size_of_the_chunk = *((size_t *)((char *)p + 8)) - 1;
    2 A9 i5 @  X  O        size_of_the_previous_chunk = *((size_t *)((char *)p));
    ) t$ E% M8 B2 A5 a        printf("chunks[%d]: %p, size = %li, prev = %li\n",, R" ]! G4 E1 ]
                  i, p, size_of_the_chunk, size_of_the_previous_chunk);
    . T" p2 _' ~+ f8 C! B    }
      H. @7 I& ^  F, o- D% U    return (EXIT_SUCCESS);1 z( x, T9 Z! d& [
    }
    8 w9 p# a$ c, i- c% R& {9 ]编译运行输出:( {1 i5 l8 Q# T% v6 b2 g
    root@3e8650948c0c:/ubuntu# gcc test.c -o test
    - @! L9 H4 L( O  _9 p$ {: Jroot@3e8650948c0c:/ubuntu# ./test% k# O* j& g# R  _0 I" ^
    0x559721de42600 |$ f% c# G1 y: y/ O8 Z
    0x559721de4a80# F+ \1 b6 @: }& t' n
    0x559721de5290
    ( Y# a9 t" ?& G3 l$ S% {( E0x559721de5ea0  U8 K( Z' S" W' x" x/ y/ P
    0x559721de6eb0$ w' C2 ]  f1 Q, b) o
    0x559721de82c0
    2 p8 h- y: z& J/ m2 R0x559721de9ad0
    ! J1 @# K5 h+ U- y- E$ x5 t9 A0 g0x559721deb6e0
    ; q8 I" Y# P: A8 Z0x559721ded6f0
    ( [$ p  b' `) b0 F4 ^0x559721defb00
    $ L  }) n; y5 @4 m7 i  O2 C& @  b6 Schunks[0]: 00 00 00 00 00 00 00 00 11 04 00 00 00 00 00 00
    " l" O. g9 R( C7 z1 nchunks[0]: 0x559721de4250, size = 1040, prev = 0/ ]1 r/ J, U4 G" |* `' G1 ?" }8 V% p/ q
    chunks[1]: 00 00 00 00 00 00 00 00 11 08 00 00 00 00 00 00! s( f: J0 b# z$ t
    chunks[1]: 0x559721de4a70, size = 2064, prev = 0
    # C# q; j$ h6 D( s. H5 I# nchunks[2]: 00 00 00 00 00 00 00 00 11 0c 00 00 00 00 00 007 k# t- s8 v! ]
    chunks[2]: 0x559721de5280, size = 3088, prev = 0
      E3 Q: W! g- I9 P: H6 ^chunks[3]: 00 00 00 00 00 00 00 00 11 10 00 00 00 00 00 00
    : G, J6 C0 S4 v4 ?: Nchunks[3]: 0x559721de5e90, size = 4112, prev = 0
    + }# i9 R; H; qchunks[4]: 10 10 00 00 00 00 00 00 10 14 00 00 00 00 00 00
    + L( o5 z" e& D  N& Schunks[4]: 0x559721de6ea0, size = 5135, prev = 4112
    , v1 T7 v0 c% [* @chunks[5]: 00 00 00 00 00 00 00 00 11 18 00 00 00 00 00 00
    # c# G! K  `2 ^chunks[5]: 0x559721de82b0, size = 6160, prev = 0
    3 m  V  D; C8 c7 W$ y" e% R. Vchunks[6]: 00 00 00 00 00 00 00 00 11 1c 00 00 00 00 00 00
    7 W, v! F: W8 `2 F5 w  B; o9 v" Rchunks[6]: 0x559721de9ac0, size = 7184, prev = 0
    ! t8 `- Q' p' m) Hchunks[7]: 00 00 00 00 00 00 00 00 11 20 00 00 00 00 00 00
    9 @% M: P8 ~# a$ l8 p' f* C! y  Cchunks[7]: 0x559721deb6d0, size = 8208, prev = 0
    4 u- k/ b( j/ p9 s2 ?6 lchunks[8]: 10 20 00 00 00 00 00 00 10 24 00 00 00 00 00 00
    * c) o6 S! u& M7 ]/ h6 ~* qchunks[8]: 0x559721ded6e0, size = 9231, prev = 8208. i2 h5 K& ?  ]( b) i' P# I
    chunks[9]: 00 00 00 00 00 00 00 00 11 28 00 00 00 00 00 007 e# ?4 [- D% M! G4 T
    chunks[9]: 0x559721defaf0, size = 10256, prev = 0
    . B: U/ @0 a- i; g; B( f1 B

    结果可以看出,malloc返回的地址往前的16个字节可以表示已经分配的内存大小, 如图:
    ) y& S' S+ T1 e( I5 ?$ `5 J
    微信图片_20201112205924.png

    注意上述是没有调用free释放内存的结果,然而malloc只用了8个字节表示已经分配的内存大小,那么另外8个字节被用来表示什么含义呢,看下malloc函数的注释:

    1055 /*1 l3 j. n2 K. ~/ P
    1056       malloc_chunk details:* i0 P5 c, \, {" x
    1057    . X- @( e* c! ]
    1058        (The following includes lightly edited explanations by Colin Plumb.)
    3 q& R  p* `4 [, x+ {1059    2 V3 T) n, F* ], w2 a
    1060        Chunks of memory are maintained using a `boundary tag' method as
    + {) i  k8 k5 T9 [0 w( `% t7 @1061        described in e.g., Knuth or Standish.  (See the paper by Paul
    # q. }! |/ y* X4 o" a1062        Wilson ftp://ftp.cs.utexas.edu/pub/garbage/allocsrv.ps for a
    ! p" i9 a1 q  ~) l1063        survey of such techniques.)  Sizes of free chunks are stored both
    9 J: R6 D6 M; F1064        in the front of each chunk and at the end.  This makes& G8 \4 E, w( Z: d8 q
    1065        consolidating fragmented chunks into bigger chunks very fast.  The
    + \. L; c' c+ _; Z, ?/ i0 k1066        size fields also hold bits representing whether chunks are free or
    ) z8 a) ]4 B9 E9 }& l0 U( A$ r, s1067        in use.
    , Z- ^; s$ M$ \5 f1068   
    & n: j3 \( S- y) Y1069        An allocated chunk looks like this:- {$ v! V! D- x; K5 }
    1070    8 ?" v' f6 o& n; c1 P- {$ o# @, ]
    1071    $ B% y; Q& {0 @. Y( \% x
    1072        chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    ) ^# S) x! _8 _; @1073                |             Size of previous chunk, if unallocated (P clear)  |
    * s  E* A: G7 |7 ^) A1074                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    ( X% Q2 E8 u( O  D* ]1075                |             Size of chunk, in bytes                     |A|M|P|2 N( I2 B4 k; ~0 H0 k; v: T3 l
    1076          mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    % X5 Y% E! E/ Z1077                |             User data starts here...                          ." x2 }: e" h' D: O: o- h
    1078                .                                                               .( x1 g$ Y. h& d) g5 ?5 U+ Q1 V8 o
    1079                .             (malloc_usable_size() bytes)                      .
    # t- @% @2 n: V- g1080                .                                                               |
    0 T- V6 N5 c# o1081    nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+, v; D. U' L( u" C& |
    1082                |             (size of chunk, but used for application data)    |
    / Z4 j+ A# _, i2 m8 ]0 \) Y" t1083                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+  ^' h% z' m% w8 o
    1084                |             Size of next chunk, in bytes                |A|0|1|5 v7 x( M" H- J
    1085                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+4 o  o2 T; F/ O5 F9 D* o! u
    1086    % u2 {/ i: C: S
    1087        Where "chunk" is the front of the chunk for the purpose of most of
    # U- _1 g7 V4 ]! _+ z: F+ A; c1088        the malloc code, but "mem" is the pointer that is returned to the/ L' `4 u4 [! X) u' T, G
    1089        user.  "Nextchunk" is the beginning of the next contiguous chunk.4 O( t) y1 w8 r- ~% ?5 R5 v, Y

    可以看出这16字节有两个含义,前8个字节表示之前的空间有多少没有被分配的字节大小,后8个字节表示当前malloc已经分配的字节大小,通过一段调用free的代码查看:

    #include <stdio.h>
    : o, K0 V" [& h. \% \/ M& [' S#include <stdlib.h>
    * P& o! r5 L$ e0 w% L#include <unistd.h>- \3 S% K& A, ^
    + g% O* a: O& Z- {  ?
    /**                                                                                            
    % ^; X6 s& C# v) R" L * pmem - print mem                                                                           
    2 r! w" N7 ]5 p * @p: memory address to start printing from                                                   
    , T3 l! R0 q0 s * @bytes: number of bytes to print                                                            & P. ^  }" B# h& z8 z0 G6 |+ X
    *                                                                                             
    6 m( G- }/ @2 K/ x; v * Return: nothing                                                                             5 ^4 @( ~- x4 e1 Z' B
    */1 j6 T2 F& R0 v+ e' p& |: O' I
    void pmem(void *p, unsigned int bytes)
    6 N( p3 L3 p- j8 X5 }{# v# ?8 Q+ V4 \2 `
        unsigned char *ptr;2 X2 s  I0 L2 ]
        unsigned int i;
    1 b1 v3 @/ X/ f6 l' ~6 q7 {
    & ]# i' L7 M+ c" t    ptr = (unsigned char *)p;! {6 @8 ?/ ?: o& V" A: ^
        for (i = 0; i < bytes; i++)
    1 L' [8 p  [% Z  K& o8 g) I8 t2 k    {7 E8 e4 d  d4 E" @  I( j
            if (i != 0)5 J. Q* H: ^8 A
            {
    " ]6 _+ m. ?1 P            printf(" ");) k9 A% R$ q% r& g
            }
    1 w# Y* d6 b1 o4 n        printf("%02x", *(ptr + i));& @2 q7 M8 t% B1 E
        }
    - N  D/ z6 H% Y7 a5 j    printf("\n");7 n( m; t* k* F2 a8 i
    }. m$ t4 E5 U% L- }- e* u
    / K# X7 M4 Z3 R" @4 k& v
    /**0 S/ b+ r, v0 a+ M9 b
    * main - confirm the source code
    $ ?: F. k3 [: E% u6 |# L3 K *3 F/ S0 m% U/ L8 p+ X$ D/ b
    * Return: EXIT_FAILURE if something failed. Otherwise EXIT_SUCCESS) q' M3 ]% L8 E$ F8 k" f
    */* i) Q2 ?* r8 `& i! L
    int main(void)
    - u4 ]+ e# e/ ^# m# S5 ]{) I+ T* \6 L  p" g; L  V3 N6 d) u
        void *p;
    / r5 A3 s1 o/ W5 I    int i;
    & r0 D( R  d; g( X7 q    size_t size_of_the_chunk;
    # Y* X7 k8 u/ P. V) v2 z9 ?    size_t size_of_the_previous_chunk;) `$ }& b% p- j2 f" M0 k
        void *chunks[10];6 F' m9 \1 [4 R

    " l4 h! O9 B/ ^. }  w    for (i = 0; i < 10; i++)
    * z( w6 W1 ^. b* s3 @. }    {. t  g( f, B1 P' G9 n! Y
            p = malloc(1024 * (i + 1));: j) u/ T& d% ^8 m+ e: r$ o& G
            chunks = (void *)((char *)p - 0x10);
    + }, U6 Q7 Y" m% Z; l/ q2 X) g        printf("%p\n", p);
    , [) m2 V# F" O" i    }
    & u" A  y! h1 P    free((char *)(chunks[3]) + 0x10);* k" `* T0 L! ?: F: @) |
        free((char *)(chunks[7]) + 0x10);
      L1 [  j1 L2 ]8 o, [) `    for (i = 0; i < 10; i++)
    + x7 |0 U6 P  ?7 o    {2 {, S$ o* I+ F# j) m
            p = chunks;, i* S$ }1 j/ T1 v0 m9 t- H9 X
            printf("chunks[%d]: ", i);
    / U& }2 ~6 ?2 v( [        pmem(p, 0x10);
    4 ?6 \. D2 D$ G+ t        size_of_the_chunk = *((size_t *)((char *)p + 8)) - 1;7 ?* u6 y8 a' W+ n
            size_of_the_previous_chunk = *((size_t *)((char *)p));
    8 a* y& F* J) K        printf("chunks[%d]: %p, size = %li, prev = %li\n",
    / r( u0 s# Y4 b. S5 q              i, p, size_of_the_chunk, size_of_the_previous_chunk);
    8 Q- u3 N7 l) R) L3 B+ |' C    }3 x- i1 j6 [* g% N
        return (EXIT_SUCCESS);
    4 D) x1 N- }& k4 ^/ h7 p}
    8 \  [  F2 G8 J. a. ^7 |8 H4 q2 f( r+ I( \; v" ^* [3 b5 y
    编译运行输出:
    7 N( h( M/ ]; Broot@3e8650948c0c:/ubuntu# gcc test.c -o test  T% F3 [+ v: v! f& ^; R
    root@3e8650948c0c:/ubuntu# ./test4 M4 j5 _$ }& N& A$ m9 M
    0x55fbebf20260+ ~' t. f: e1 s( _: X) q7 n# w4 ]
    0x55fbebf20a80
    1 c' C% ^3 G# s6 f3 @( p0x55fbebf21290
    - p) [  z; w* r3 t9 t% C0x55fbebf21ea00 C: Z0 n6 b) c  Q# c4 `
    0x55fbebf22eb04 W3 M3 o- x" u2 s
    0x55fbebf242c0
      M1 R- \1 J: B* e1 [' i' p6 }0x55fbebf25ad0, F9 ~3 E) `" n* j
    0x55fbebf276e06 [( C# g  R! d
    0x55fbebf296f0
    " o5 q' O# r9 S; E0x55fbebf2bb00( ~/ g- y. Q2 h8 t& R
    chunks[0]: 00 00 00 00 00 00 00 00 11 04 00 00 00 00 00 00
    . q: W& S) {9 j! F) Jchunks[0]: 0x55fbebf20250, size = 1040, prev = 0
    & L0 g9 X0 b' `% o: `chunks[1]: 00 00 00 00 00 00 00 00 11 08 00 00 00 00 00 00' L. `9 B* R# i0 _7 O* v  J+ W2 U
    chunks[1]: 0x55fbebf20a70, size = 2064, prev = 0
    9 w; b5 a2 A* `8 r9 ^& ^chunks[2]: 00 00 00 00 00 00 00 00 11 0c 00 00 00 00 00 00- G# j% K, B. x& z3 Y
    chunks[2]: 0x55fbebf21280, size = 3088, prev = 0
    $ P  H; o. U6 E4 o9 [; ?( V5 [. x: Tchunks[3]: 00 00 00 00 00 00 00 00 11 10 00 00 00 00 00 00" N: t; ]: W, V& S1 S; Y! Q. F3 E
    chunks[3]: 0x55fbebf21e90, size = 4112, prev = 0
      v: p2 C/ i, s! g8 P+ mchunks[4]: 10 10 00 00 00 00 00 00 10 14 00 00 00 00 00 00* l% z$ D( q! N, X
    chunks[4]: 0x55fbebf22ea0, size = 5135, prev = 4112
    0 n7 t2 n5 _. U! J* W. Tchunks[5]: 00 00 00 00 00 00 00 00 11 18 00 00 00 00 00 008 P9 |0 Q/ c8 @* C9 t7 m
    chunks[5]: 0x55fbebf242b0, size = 6160, prev = 0
    ( M" u+ c2 Y5 Ichunks[6]: 00 00 00 00 00 00 00 00 11 1c 00 00 00 00 00 00
    ; f) }' f: f' R) B! e  Rchunks[6]: 0x55fbebf25ac0, size = 7184, prev = 0  M8 J" B! {/ v: T  Y
    chunks[7]: 00 00 00 00 00 00 00 00 11 20 00 00 00 00 00 00
    9 u: G! T# N+ G' C2 G# G. A  I( Kchunks[7]: 0x55fbebf276d0, size = 8208, prev = 0
    3 \# o, [9 l( tchunks[8]: 10 20 00 00 00 00 00 00 10 24 00 00 00 00 00 008 m& h5 w- H% B* ~1 D- I) z) ]& a
    chunks[8]: 0x55fbebf296e0, size = 9231, prev = 8208
    ; Z& `; ^5 J  f+ ~& n. mchunks[9]: 00 00 00 00 00 00 00 00 11 28 00 00 00 00 00 00; k0 }6 f- e: S' X8 D
    chunks[9]: 0x55fbebf2baf0, size = 10256, prev = 0/ b! c- G9 B' |- U8 {' A' k

    程序代码通过free释放了3和7数据块的空间,所以4和8的前8个字节已经不全是0啦,和其它不同,它们表示之前数据块没有被分配的大小,也可以注意到4和8块的后8个字节不像其它块一样需要加1啦,可以得出结论,malloc通过是否加1来作为前一个数据块是否已经分配的标志,加1表示前一个数据块已经分配。所以之前的程序代码可以修改为如下形式:

    #include <stdio.h>
    $ h* N' X6 ?* l# v#include <stdlib.h># q5 Q# g+ N) j6 z; R; S! a
    #include <unistd.h>+ Q0 c0 d" W" m3 q) o! t
    ) F: M) o$ j2 P7 n6 E! s
    /**                                                                                            3 a- ^$ K* X4 |, B8 L- R4 ?
    * pmem - print mem                                                                            9 J6 J, R7 p* B) {
    * @p: memory address to start printing from                                                   
    : D& U* V/ `9 m, u* K" Z * @bytes: number of bytes to print                                                            * n* S) w+ L# z4 C) l  a, d
    *                                                                                             
    1 F, T) d* O3 z * Return: nothing                                                                             & r) j1 ~1 v4 K: ^  q
    */
    ) t/ K. U4 `$ |; T6 _void pmem(void *p, unsigned int bytes)+ q- F0 J% `# j* d) L, F
    {' ^" i6 l1 V/ I0 o
        unsigned char *ptr;
    $ E+ H( A: ~: Z5 N    unsigned int i;) ^: V; L1 ?3 I: a; ]. ^) K/ O# o
    8 I- @9 C! ?. K# B% W( I& l$ b5 L# z
        ptr = (unsigned char *)p;
      w- }, r1 L2 f1 h" h5 c    for (i = 0; i < bytes; i++)6 g1 M. F- a! V7 ^
        {
    ( {2 _: C, @! n8 T6 [: |: q- |; o        if (i != 0)
    6 [- o, K0 c9 {* C+ M        {& w5 a1 G" {1 F3 Q( F. Z
                printf(" ");
    ! D! v7 S5 ^. E. y        }
    $ o& E6 ]/ b4 _8 _        printf("%02x", *(ptr + i));
    ! G- v9 r( a# y4 }    }3 ]# c) x* d7 u4 n/ B5 Y
        printf("\n");
    / o. J' G! ~& A$ G3 i" I}
    ( r  P3 E0 Q" {2 L
    2 K: l/ ^) n. Q  Z. B/**4 X& j  \7 ^7 c# w  W6 K- ~  Y! z% B
    * main - updating with correct checks0 j! Y2 D6 U% u( @3 O# l" }. i  \
    *
    / v) f: }2 h" w: p) U6 ~9 L9 B * Return: EXIT_FAILURE if something failed. Otherwise EXIT_SUCCESS- W) K. i  t6 o' Q
    */) Z7 D. N/ v9 s' h7 @' W
    int main(void)
    : U3 N; ?4 J0 ~' ]{: Q3 k4 i& F; n8 R. j2 v
        void *p;
    - n' O1 a! P3 R; n8 b    int i;
    8 A2 Z9 l7 V5 y) D. Z  }+ \) a    size_t size_of_the_chunk;% y4 `7 O6 \, x- M- w
        size_t size_of_the_previous_chunk;
    ) G0 l5 C# o: ]  t) X4 ~- q    void *chunks[10];
    ! o0 @" X6 `6 Y    char prev_used;
    $ E0 Z5 X8 W) u" Q. W' s
    $ I0 k, R( o- P+ F" Z  i    for (i = 0; i < 10; i++)
    1 p6 B' q2 D9 I! B( X    {
    & ?8 k  y, ~' B        p = malloc(1024 * (i + 1));9 ?+ X: U- ?5 U1 {+ y3 G* N
            chunks = (void *)((char *)p - 0x10);
    / e; [9 I3 }% h) G) R7 B    }; N2 ~0 H6 |0 b" G7 \% [; \$ x
        free((char *)(chunks[3]) + 0x10);
    - m1 x. l* }& F8 x    free((char *)(chunks[7]) + 0x10);& b% b- _% I2 A2 s7 }/ ~9 N
        for (i = 0; i < 10; i++)" a5 n- h$ ?" G/ H
        {
    8 w; J8 C% K( m7 M        p = chunks;" m% O& a+ F9 W  _: I
            printf("chunks[%d]: ", i);( W) W1 _5 F# C+ G* i" T
            pmem(p, 0x10);4 y. Y1 a& q4 F/ z$ D
            size_of_the_chunk = *((size_t *)((char *)p + 8));
    . f- o  H7 Z2 }        prev_used = size_of_the_chunk & 1;
    $ d; V( l* H/ G3 i        size_of_the_chunk -= prev_used;
    - @7 v5 _$ [1 C2 k        size_of_the_previous_chunk = *((size_t *)((char *)p));
    % ]3 y7 Y) s: D7 R& d        printf("chunks[%d]: %p, size = %li, prev (%s) = %li\n",; S+ u/ K4 j* }1 {# `% [
                  i, p, size_of_the_chunk,
    : R% L& x* J* W# l. y) v              (prev_used? "allocated": "unallocated"), size_of_the_previous_chunk);
    3 C1 l' g% [' a- I    }) W& w$ L) r$ t! Q4 x+ Q+ a- {
        return (EXIT_SUCCESS);' @4 ^2 b: m1 [2 P5 p
    }" S3 k3 U/ V% ]- `! Y# `- x
    编译运行输出:! ?9 f8 Y& C& L4 y4 e" U
    root@3e8650948c0c:/ubuntu# gcc test.c -o test
    2 M# _# |, Q( }5 p0 Mroot@3e8650948c0c:/ubuntu# ./test9 T& E1 H. f/ B
    chunks[0]: 00 00 00 00 00 00 00 00 11 04 00 00 00 00 00 00
    2 n' z  A# A) K7 m; gchunks[0]: 0x56254f888250, size = 1040, prev (allocated) = 0
    % @( k0 u$ M: F7 Y7 }chunks[1]: 00 00 00 00 00 00 00 00 11 08 00 00 00 00 00 00- [+ e) W1 l1 @
    chunks[1]: 0x56254f888660, size = 2064, prev (allocated) = 05 t0 D$ @# L7 B* t- s3 O. h* R
    chunks[2]: 00 00 00 00 00 00 00 00 11 0c 00 00 00 00 00 00
    - ~( J! M; @" f, ?& d- {chunks[2]: 0x56254f888e70, size = 3088, prev (allocated) = 0
    ( K3 @" I2 D2 d& o8 Y( s. d# X* bchunks[3]: 00 00 00 00 00 00 00 00 11 04 00 00 00 00 00 00" m" W) M" R" ^" g4 `1 @/ F8 N5 Z  d
    chunks[3]: 0x56254f889a80, size = 1040, prev (allocated) = 0
    4 e% b% }$ l' k5 X0 Hchunks[4]: 00 0c 00 00 00 00 00 00 10 14 00 00 00 00 00 00
    ; R: e6 ?) e; W8 z1 Vchunks[4]: 0x56254f88aa90, size = 5136, prev (unallocated) = 3072
    " A7 t. p5 _- T+ m0 u' c- O8 g3 w: Bchunks[5]: 00 00 00 00 00 00 00 00 11 18 00 00 00 00 00 00' _  Q0 d$ J. E0 R7 e
    chunks[5]: 0x56254f88bea0, size = 6160, prev (allocated) = 0
    $ {) g& I4 q# o0 fchunks[6]: 00 00 00 00 00 00 00 00 11 1c 00 00 00 00 00 005 R9 T7 `2 |' m  A' F0 Y
    chunks[6]: 0x56254f88d6b0, size = 7184, prev (allocated) = 0. f7 L! V4 ]! |, M/ ]* M5 s
    chunks[7]: 00 00 00 00 00 00 00 00 11 20 00 00 00 00 00 00' j5 \9 ]: Q) }9 f
    chunks[7]: 0x56254f88f2c0, size = 8208, prev (allocated) = 0
    + G5 x8 u; M7 ]" Z- ~9 G* Wchunks[8]: 10 20 00 00 00 00 00 00 10 24 00 00 00 00 00 00
    ' p/ d; P1 \, E7 R1 T; [# k/ q* b* bchunks[8]: 0x56254f8912d0, size = 9232, prev (unallocated) = 8208
    - k5 s% y6 Q) k% vchunks[9]: 00 00 00 00 00 00 00 00 11 28 00 00 00 00 00 006 E) p. P9 d* k5 \: J. x
    chunks[9]: 0x56254f8936e0, size = 10256, prev (allocated) = 0" K$ P0 F  G6 ]5 e$ Y; E# z
    堆空间是向上增长吗?

    通过代码验证:

    #include <stdio.h>
    / t1 ^8 t) Q$ S2 |* S#include <stdlib.h>& |; j3 d7 [0 ^5 |( o
    #include <unistd.h>
    & Z9 ~" Q( O2 U$ t$ Y+ ?
    , Q' w' }4 [4 X2 O  Q/**
    ( O1 c, ^9 B! B; }* _4 a) D$ l * main - moving the program break
    5 b% ]4 b8 a' E9 {6 K3 {4 b *3 |8 Y8 j9 O. [+ ^6 A7 g
    * Return: EXIT_FAILURE if something failed. Otherwise EXIT_SUCCESS
    6 a7 ^( e) v4 e1 E" ?% V; Z) p" e */
    2 y) J. `4 l( Q3 R$ zint main(void)
    8 P# ]% B1 A4 S$ c$ J! ^( t{/ R1 O# h3 J  {- n$ \) S
        int i;
    9 s& m& c) \  E% }
    2 c% Q) p% D7 s, E3 V5 g6 c% x6 R    write(1, "START\n", 6);% H% [& j) I+ g: [! L+ l
        malloc(1);
    ) E/ ]' p. Y1 B    getchar();
    1 O( A) c5 p5 b" U3 Z4 |    write(1, "LOOP\n", 5);+ d+ [; N2 H* `
        for (i = 0; i < 0x25000 / 1024; i++): @- o. m3 C1 A  S
        {* p- D4 r+ k) O$ r1 |- U
            malloc(1024);
    ; b) m9 W7 ~/ `6 t4 ^  ~    }
    . y& F2 i' t/ Z1 n! t% Y    write(1, "END\n", 4);  ~4 y1 X2 D! x% Y% q4 t7 \
        getchar();* L: ]5 E" `3 V( S/ e1 e  Q) q% F
        return (EXIT_SUCCESS);
    / M; X  ?, V4 `, L& M, f* C}# c$ n" X- y) E1 T) C, T# s! ?
    编译运行部分摘要输出:
    : t! F! j' D% t- k; f9 C3 w& Lroot@3e8650948c0c:/ubuntu# gcc test.c -o test, J" s0 G* |4 F, s2 N. \, D  B1 K3 |
    root@3e8650948c0c:/ubuntu# strace ./test
    , ]1 E$ i7 x" s5 K0 G$ x" yexecve("./test", ["./test"], 0x7ffe0d7cbd80 /* 10 vars */) = 0' v+ C6 r4 s+ ?  b
    brk(NULL)                               = 0x555a2428f000% p5 h3 ]* @: k# [& R2 |
    access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
    " h  b5 O' t* l/ a& caccess("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)( O. K* s7 d: L4 n5 s3 s' z
    openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
    + q8 _! [) r/ ]6 @2 K6 @fstat(3, {st_mode=S_IFREG|0644, st_size=13722, ...}) = 08 S4 n2 B  W8 f! \
    mmap(NULL, 13722, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f6423455000
    9 K; M/ S+ Q! n6 ]& H* |* Qclose(3)                                = 0
    ! d7 D' q  M- e; A+ Zaccess("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
    1 T( B5 `. z$ i' j9 h7 X5 s2 dopenat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3. }" V# u0 y1 H1 x
    read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\260\34\2\0\0\0\0\0"..., 832) = 8325 h& \8 }: E5 {, F8 j6 T
    fstat(3, {st_mode=S_IFREG|0755, st_size=2030544, ...}) = 0+ K* b8 T: ]9 L) S. P$ X9 ~0 `
    mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f6423453000
    1 ]/ c: r; ^* z$ @mmap(NULL, 4131552, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f6422e410001 u, K3 h1 S$ ^3 p
    mprotect(0x7f6423028000, 2097152, PROT_NONE) = 0
    ( }3 _2 s0 d6 J+ ommap(0x7f6423228000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7f6423228000/ R/ b: \0 e* `' v  v+ |% h
    mmap(0x7f642322e000, 15072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f642322e000) F6 c4 D9 u5 }4 C0 T
    close(3)                                = 0' x' ?+ z1 p- @  e- `$ q6 n
    arch_prctl(ARCH_SET_FS, 0x7f64234544c0) = 0# M' R! i# Y: p& B8 V4 q7 n
    mprotect(0x7f6423228000, 16384, PROT_READ) = 0
    5 M0 L8 V/ P3 h- P+ k; Kmprotect(0x555a22f5f000, 4096, PROT_READ) = 08 \2 U6 d% R- @, n1 Z
    mprotect(0x7f6423459000, 4096, PROT_READ) = 0% l5 r" i4 f5 t4 Y+ x
    munmap(0x7f6423455000, 13722)           = 0
    : c% k& {& P7 O$ s+ t1 v5 Qwrite(1, "START\n", 6START% G8 F/ l" R# B: m4 W6 |
    )                  = 6- Y' U. x! G8 u1 ]0 ^6 p/ r
    brk(NULL)                               = 0x555a2428f000
    - y. r+ `. c# hbrk(0x555a242b0000)                     = 0x555a242b0000
    5 l- x/ s- }' g( L! D' Gfstat(0, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0* ?" @* @3 W- N, {2 x$ o" A
    read(0,
    ! x# i9 V1 g* H, x* d" P$ U# v"\n", 1024)                     = 1
    / O4 N6 x6 R+ D  j# ywrite(1, "LOOP\n", 5LOOP& i, H7 m+ Y& D; E: |
    )                   = 5! ~5 _* E3 Z# |  w9 ]: V+ U  f
    brk(0x555a242d1000)                     = 0x555a242d10004 p- r1 Y: U5 t" @. I
    brk(0x555a242f2000)                     = 0x555a242f2000) g) }- ?9 H/ [3 j+ L1 [
    brk(0x555a24313000)                     = 0x555a24313000
    2 l) ?. g6 S; {$ Wbrk(0x555a24334000)                     = 0x555a243340008 G3 ]( @+ @- G8 ^9 M+ ]7 g
    brk(0x555a24355000)                     = 0x555a243550007 K$ ]4 E) Z5 T; r6 f) W$ _& g
    brk(0x555a24376000)                     = 0x555a24376000
      o( ]9 W) V# J" b, r% c" T" Ubrk(0x555a24397000)                     = 0x555a243970000 `% ~" H- u! N
    brk(0x555a243b8000)                     = 0x555a243b80006 D5 E) W# n) O+ W
    brk(0x555a243d9000)                     = 0x555a243d9000+ `4 f3 {8 R7 }0 p& q5 u! @9 S
    brk(0x555a243fa000)                     = 0x555a243fa0000 e0 [; y! @+ a& Z6 p

    可以看出堆空间是向上增长的.

    随机化地址空间布局

    从开始到现在运行了好多个进程,通过查看对应进程的maps,发现每个进程的heap的起始地址和可执行程序的结束地址都不紧邻,而且差距还每次都不相同.

    [3718]: 01195000 – 00602000 = b93000
    " W. f( u* ~3 `; C[3834]: 024d6000 – 00602000 = 1ed40009 P; F+ p5 `0 P
    [4014]: 00e70000 – 00602000 = 86e000
    $ r! O0 q2 E" x" S[4172]: 01314000 – 00602000 = d12000* j/ T5 e3 F' q" m( w  N# Y0 }
    [7972]: 00901000 – 00602000 = 2ff000  I  E8 q3 ]) `( S) N! _

    可以看出这个差值是随机的,查看fs/binfmt_elf.c源代码

    if ((current->flags & PF_RANDOMIZE) && (randomize_va_space > 1)) {
    , D+ _, i  ~" V: {                current->mm->brk = current->mm->start_brk =' e) O- K4 G4 ?  b
                            arch_randomize_brk(current->mm);: A9 p+ m' K$ @6 s4 O0 o9 g4 U3 C3 o
    #ifdef compat_brk_randomized8 N8 J$ r2 F/ q; s  m; Q8 s
                    current->brk_randomized = 1;1 x0 r( B* V  }  G: J1 [6 h' _
    #endif
    ( V$ B: S$ g( s; `        }4 Q& d) N+ W7 x8 G" R7 \: {
    // current->mm->brk是当前进程程序中断的地址  B% r, J$ u) n: `1 d0 x

    arch_randomize_brk函数在arch/x86/kernel/process.c中

    unsigned long arch_randomize_brk(struct mm_struct *mm)% `3 w9 f+ C+ p) }( Z& X
    {# m0 W: r% g5 A% b4 i
            unsigned long range_end = mm->brk + 0x02000000;8 M  t6 l& t' p1 f
            return randomize_range(mm->brk, range_end, 0) ? : mm->brk;
    1 k$ U9 A; y2 |: l$ f' g; B}
    8 q6 t7 l9 W; G2 ?% x

    randomize_range函数在drivers/char/random.c中

    /*
    8 e- a! h5 }6 x1 s/ C0 P * randomize_range() returns a start address such that
    " D; y- Q% p' y/ \ *
    7 Y0 w. `, z! ]6 P7 c; j- K *    [...... <range> .....]7 \- ]% r1 i: Q! y
    *  start                  end$ }  u5 Y& l0 |0 ~* M5 ~* E
    *
    * w. H( E1 e4 c1 H% | * a <range> with size "len" starting at the return value is inside in the6 W- p' c2 ]% |- G& K. U0 z2 ?
    * area defined by [start, end], but is otherwise randomized.
    0 G3 Y& g! S- i# W5 F/ H! G */
    " A6 x4 m7 U) \$ f: uunsigned long, Z: c9 H7 e$ f3 j( t
    randomize_range(unsigned long start, unsigned long end, unsigned long len)
    ' w" _  |' t; x, h{( @1 C& x$ J0 C/ U/ v7 e4 Q9 B0 i' f
            unsigned long range = end - len - start;
    4 ~- b0 [' C$ v% N( q+ k1 X& s; d, n) P! K: U  q, r
            if (end <= start + len)- y7 ]0 }2 S8 S/ @' U: o3 H! M
                    return 0;% i( K9 \' ?" P# g' V1 w
            return PAGE_ALIGN(get_random_int() % range + start);
    / ^9 D8 |, e6 f0 O) w- n7 o- _}
    % ~9 W  L$ M/ s

    可以看出上面所说的这个差值其实就是0-0x02000000中的一个随机数,这种技术称为ASLR(Address Space Layout Randomisation),是一种计算机安全技术,随机安排虚拟内存中堆栈空间的位置,可以有效防止黑客攻击。通过以上分析,可以画出内存分布图如下:0 `% Y4 j9 u# z! f0 ]6 i0 I) b
    微信图片_20201112205928.png

    malloc(0)发生了什么?

    当调用malloc(0)会发生什么,代码如下:

    #include <stdio.h>/ k# _9 a7 w5 @3 C* q/ \
    #include <stdlib.h>
    + r: {+ k! v" a  Q- T#include <unistd.h>
    # {/ ]; _' V# o5 q! D
    3 E" z2 C5 N/ U4 A% b& Z- P2 v/**                                                                                            ) m/ ?- q4 Y7 h* z- V
    * pmem - print mem                                                                            - R/ a# U3 s6 O; v" e# D
    * @p: memory address to start printing from                                                   ) a% {% P& M$ z$ y% C
    * @bytes: number of bytes to print                                                            2 V% E8 m# U4 r# R
    *                                                                                             
    3 H# l" {" M: n" {" h. Y1 G * Return: nothing                                                                             ( w( j4 A* P9 I) `2 j7 ~2 F* a
    */8 M3 t8 h6 D& _3 a7 V) d
    void pmem(void *p, unsigned int bytes): A& ~" p( Y4 R: o/ C9 q  I
    {
    - I' v" p9 m- k& x: m    unsigned char *ptr;
    5 k& F1 Y* F1 n: r/ d    unsigned int i;9 z, r4 _9 A* t# m0 E

    ! @1 O" h9 k. {' M% W, \" K5 S    ptr = (unsigned char *)p;
    ! h! J& S+ Z) C. x6 ~    for (i = 0; i < bytes; i++)4 {# E, h" f) x8 N* p
        {& f& K8 M" B# G& I$ u6 w& H9 E
            if (i != 0)
    1 J* S# _: T6 i* E9 ~0 S- `7 F+ J# Q        {% h# {" H! ~* D  k+ K$ ]' O
                printf(" ");
    3 E4 }( h3 z) J; a( F& ^: `        }6 A" ]7 n( y. _) p6 i
            printf("%02x", *(ptr + i));
    # D$ d7 W" F6 B& G+ V    }
    ) z9 j7 A6 [, P$ j3 C) N% s/ t    printf("\n");
    $ h( u! b: f+ F' Q) Z1 I0 D1 E}
    $ x8 D' M, k' i: k! S
    5 ]0 Q+ h: I1 ~) O0 e/**! a; G  y: W, V1 I/ [% Y, l& ?
    * main - moving the program break$ s" [8 O6 E3 e$ t$ ?+ T$ j
    *
    : X) r* }7 R0 R6 K * Return: EXIT_FAILURE if something failed. Otherwise EXIT_SUCCESS- k* S) o6 m: t: C$ J
    */
    " P  T7 m  j( N  P6 cint main(void)) @& }3 j$ J9 C  {2 X
    {7 x+ Z- n2 `/ v  H
        void *p;
    ( ?- {1 A9 ]5 ^8 {- C# ?8 \    size_t size_of_the_chunk;
    0 W1 \3 S% N" L  A7 z9 @, `    char prev_used;
    1 A! {- E. q; j( t% i9 `
    - I% X! K% o1 v    p = malloc(0);2 F5 D4 N" Q. W" g! c
        printf("%p\n", p);
    / }. O' a7 d! |8 B- i+ b, F    pmem((char *)p - 0x10, 0x10);$ ]5 R, N9 t+ ]! G6 [2 x% G; M
        size_of_the_chunk = *((size_t *)((char *)p - 8));
    * D: A" z! [/ }8 {" |* s    prev_used = size_of_the_chunk & 1;
    % L) K0 ?0 G- d! |0 ?( h$ R    size_of_the_chunk -= prev_used;8 o* k  l4 a) g0 s
        printf("chunk size = %li bytes\n", size_of_the_chunk);
    / H0 g& `' b: W0 |! j) Y5 H/ D    return (EXIT_SUCCESS);
    % d5 c) E0 N' ~8 y# R7 B}/ c" x& X) S" s5 k5 [) k
    编译运行输出如下:5 C5 {; N+ [% M7 G5 J; U& e( d
    root@3e8650948c0c:/ubuntu# gcc test.c -o test
    ( B- M2 Q' K% H6 ?. \root@3e8650948c0c:/ubuntu# ./test( ]! M4 C; e% y! x
    0x564ece64b2609 X2 u+ k0 t/ o: ]& S
    00 00 00 00 00 00 00 00 21 00 00 00 00 00 00 00; M, r$ C, w( K2 n% p# t" M
    chunk size = 32 bytes+ _& l+ s' X! Y* W6 Q* U+ X

    可以看出malloc(0)实际使用了32个字节,其中包括我们之前说的16个字节头部,然而有时候malloc(0)可能会有不同的结果输出,也有可能会返回NULL.

    man malloc2 I- n* N2 l# h- Q7 F5 N' h
    NULL may also be returned by a successful call to malloc() with a size of zero! f( a! v/ C0 M$ o
    操作环境示例代码主要在两种环境下跑过:  h* w  B. C- A2 L
    ubuntu 16.04
    9 R' }( ^2 e, Ngcc (Ubuntu 7.4.0-1ubuntu1~16.04~ppa1) 7.4.08 U- t7 b9 y# w+ G# Q* A% k
    $ i# o3 h- ^* \' X% g' T6 }$ q! R& R
    ubuntu 18.04 docker
    6 Y/ \! Y, h# E# P! }( z4 E4 hgcc (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.0
    5 g/ c% [0 M$ i# Y( X% ~# Y
    本文是从这一系列文章翻译并结合自己理解提炼出来的,代码都自己实践过,有时间的也可以直接阅读英文原链接
    1 z$ y9 P6 m& J7 n3 ~+ I. i
    Hack The Virtual Memory: C strings & /proc  ) p5 s% p2 f& I2 E9 l; ~$ K
    Hack the Virtual Memory: drawing the VM diagram
    Hack the Virtual Memory: malloc, the heap & the program break
    6 H9 o/ `; D; J! q; D& y
  • 赞 收藏 评论0 发布时间:2020-11-12 21:03

    举报

    0个回答
    关于
    我们是谁
    投资者关系
    意法半导体可持续发展举措
    创新与技术
    意法半导体官网
    联系我们
    联系ST分支机构
    寻找销售人员和分销渠道
    社区
    媒体中心
    活动与培训
    隐私策略
    隐私策略
    Cookies管理
    行使您的权利
    官方最新发布
    人形机器人运动控制、感知与智能配电
    半导体创新技术与应用方向
    EE架构与软件定义汽车
    12V/48V 汽车智能配电(SPD)
    区域控制单元(ZCU)与分区架构
    关注我们
    st-img 微信公众号
    st-img 手机版