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

一文搞定虚拟内存模型和malloc内部原理

[复制链接]
gaosmile 发布时间:2020-11-19 20:22
通过/proc文件系统探究虚拟内存. V* |% G' o$ e0 k. r
我们会通过/proc文件系统找到正在运行的进程的字符串所在的虚拟内存地址,并通过更改此内存地址的内容来更改字符串内容,使你更深入了解虚拟内存这个概念!这之前先介绍下虚拟内存的定义!
虚拟内存
虚拟内存是一种实现在计算机软硬件之间的内存管理技术,它将程序使用到的内存地址(虚拟地址)映射到计算机内存中的物理地址,虚拟内存使得应用程序从繁琐的管理内存空间任务中解放出来,提高了内存隔离带来的安全性,虚拟内存地址通常是连续的地址空间,由操作系统的内存管理模块控制,在触发缺页中断时利用分页技术将实际的物理内存分配给虚拟内存,而且64位机器虚拟内存的空间大小远超出实际物理内存的大小,使得进程可以使用比物理内存大小更多的内存空间。

+ {- Y" H; ~* @0 T. G$ r
在深入研究虚拟内存前,有几个关键点:
  • 每个进程都有它自己的虚拟内存
  • 虚拟内存的大小取决于系统的体系结构
  • 不同操作管理有着不同的管理虚拟内存的方式,但大多数操作系统的虚拟内存结构如下图:
    / l/ i8 V, M: a3 p! m& L& I& a
微信图片_20201119201731.png
virtual_memory.png
上图并不是特别详细的内存管理图,高地址其实还有内核空间等等,但这不是这篇文章的主题。从图中可以看到高地址存储着命令行参数和环境变量,之后是栈空间、堆空间和可执行程序,其中栈空间向下延申,堆空间向上增长,堆空间需要使用malloc分配,是动态分配的内存的一部分。

# [4 L  o2 K3 d( K: Y; }+ b( o3 g
首先通过一个简单的C程序探究虚拟内存。' O& B% e! p4 G3 N$ I- s0 h
#include <stdlib.h>
6 L  ?1 \2 O! H7 x#include <stdio.h>3 v  B* j# X) F0 U$ G- q, C# b$ y
#include <string.h>
' r% T9 {3 n3 N% `, y+ I6 |9 d( W! Q% t* p. j
/**
1 E2 a4 K1 O- V: s7 O- l: q * main - 使用strdup创建一个字符串的拷贝,strdup内部会使用malloc分配空间,
' H/ W- a" i9 r6 k6 k * 返回新空间的地址,这段地址空间需要外部自行使用free释放
( L# @3 [! R" }1 r2 A$ @ ** e8 v  A6 @. S( C6 e
* Return: EXIT_FAILURE if malloc failed. Otherwise EXIT_SUCCESS
( o" q2 q3 S3 C2 h. h */$ l9 @2 H" V: c6 `" ~
int main(void)' \7 @: Q0 x: e) ?- \8 E, h
{; ~0 s" E5 ~1 j) Y% y0 k9 d) I
    char *s;
! t  k! ~) R+ l+ ^2 ?
7 `9 W2 A6 ^- I. a: ^# q: E* x    s = strdup("test_memory");$ w# T! R0 B* k
    if (s == NULL)! x# z% L( l  H. W* a* R# v
    {
9 |% d* R" ?) G        fprintf(stderr, "Can't allocate mem with malloc\n");- V8 b" ?  A2 d+ I" P, n' R
        return (EXIT_FAILURE);0 k* D/ j6 x4 b6 q/ X& q
    }2 I. Z8 P: e* @/ y- M
    printf("%p\n", (void *)s);2 _' C8 f2 `3 A3 p- m
    return (EXIT_SUCCESS);
9 U5 c; [7 K2 U- G- L8 i6 ^) i) _}
+ L! {  T. R/ s: u7 L
, {  s: [; b+ i! G- q编译运行:gcc -Wall -Wextra -pedantic -Werror main.c -o test; ./test$ T' o8 E( P7 h$ U$ y0 C; |
输出:0x88f0102 q6 p$ b! o5 j5 Y8 E

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

mem & mapsman proc* Z6 ~, I/ V% f

" ^9 w, s9 N1 Z/ }1 P2 a/proc/[pid]/mem7 u. G9 ^1 u- \+ ?- w4 n' E
    This file can be used to access the pages of a process's memory through open(2), read(2), and lseek(2).
2 P+ ]+ Z, a; |, N1 x' x
6 n; R# c! u8 P/proc/[pid]/maps
0 \! M9 U1 k" ~! A0 N. \, |    A  file containing the currently mapped memory regions and their access permissions.
& ]- |" v. v8 |          See mmap(2) for some further information about memory mappings., v8 [& [; [; f# F9 i

7 R6 y# X) a# A, e              The format of the file is:5 ?+ z# O  T  s/ G) ~( f

% t* L$ V, J6 S2 i  w3 w       address           perms offset  dev   inode       pathname! o7 o' X) }: u- w5 m/ @
       00400000-00452000 r-xp 00000000 08:02 173521      /usr/bin/dbus-daemon
) f) e% N  K" d- e1 Q       00651000-00652000 r--p 00051000 08:02 173521      /usr/bin/dbus-daemon  H% F  ?8 V1 m$ f  C
       00652000-00655000 rw-p 00052000 08:02 173521      /usr/bin/dbus-daemon
3 T$ f9 M" d/ U) _: j       00e03000-00e24000 rw-p 00000000 00:00 0           [heap]7 |) K+ @6 ~% y; O: L. K' E
       00e24000-011f7000 rw-p 00000000 00:00 0           [heap]
' F# G; q7 u4 N) R" W9 |/ n, D- P4 O       ...
" C0 G$ ^) _' x6 q6 u       35b1800000-35b1820000 r-xp 00000000 08:02 135522  /usr/lib64/ld-2.15.so
5 \7 G8 f9 R, s6 G: ~       35b1a1f000-35b1a20000 r--p 0001f000 08:02 135522  /usr/lib64/ld-2.15.so
9 b& I/ Q  F& z# k       35b1a20000-35b1a21000 rw-p 00020000 08:02 135522  /usr/lib64/ld-2.15.so
3 T  m9 z+ c2 k/ U& D       35b1a21000-35b1a22000 rw-p 00000000 00:00 0
. A$ b0 H7 d2 Q- O* j7 Q( i6 ]0 K; y       35b1c00000-35b1dac000 r-xp 00000000 08:02 135870  /usr/lib64/libc-2.15.so
/ S: f) m1 ]: l: [       35b1dac000-35b1fac000 ---p 001ac000 08:02 135870  /usr/lib64/libc-2.15.so: U. H8 g, O- `9 {3 l! @
       35b1fac000-35b1fb0000 r--p 001ac000 08:02 135870  /usr/lib64/libc-2.15.so7 p9 e, }/ r% n8 z, [9 u3 e, o6 g
       35b1fb0000-35b1fb2000 rw-p 001b0000 08:02 135870  /usr/lib64/libc-2.15.so2 Q4 F9 S. w8 [1 h2 V! }
       ...8 B' L  L9 w7 J  }4 i
       f2c6ff8c000-7f2c7078c000 rw-p 00000000 00:00 0    [stack:986], B) x6 I- A6 f- c& p
       ...
0 _" \" |* W1 C0 k9 A7 n& h       7fffb2c0d000-7fffb2c2e000 rw-p 00000000 00:00 0   [stack]
2 R" k0 v0 H5 U; ]7 i% S5 e+ f       7fffb2d48000-7fffb2d49000 r-xp 00000000 00:00 0   [vdso]
$ w% ]8 {- J% |. T
9 u1 [/ Y5 W- W! s3 I4 ^" t4 ^8 i% c7 h              The address field is the address space in the process that the mapping occupies.8 z. R- l0 u+ t7 b: o8 e! }  }
          The perms field is a set of permissions:& `5 Y! _2 U0 l# o0 }( ^

% V5 f. n' k5 ^8 P; |3 A                   r = read6 `5 G/ T% o/ ?0 h7 q
                   w = write! H8 n5 A! }5 b2 S% E
                   x = execute% s* m: Y1 t$ Y% }4 i+ m- x+ N3 K
                   s = shared3 w+ ^( t# o' ?; J/ q; g
                   p = private (copy on write)8 S8 a- O/ u) M7 d! H( O8 n% ?3 w
7 w" g: h% }. W' D. e. R% _6 I
              The offset field is the offset into the file/whatever;
" q6 |& u; b4 K% {; _2 ~$ t          dev is the device (major:minor); inode is the inode on that device.   0  indicates( ~9 e$ V! ]( R2 ~. W7 P. p4 t
              that no inode is associated with the memory region,
2 `, y2 P; Q" E6 P( Q          as would be the case with BSS (uninitialized data).+ m. n" [. Q7 f" d1 H+ u

  }' o$ t/ f- l              The  pathname field will usually be the file that is backing the mapping.
2 y& E+ A, q7 A. k5 V$ h& Q* m6 R. M          For ELF files, you can easily coordinate with the offset field
; z5 I) y6 B2 L. x! v" P: _              by looking at the Offset field in the ELF program headers (readelf -l).' @$ a; _7 Z- h: X; u+ I' f* j

: e$ S) v+ e, @              There are additional helpful pseudo-paths:
) q( q5 e2 N7 ^2 u- C
% }& ]1 r: }3 J                   [stack]( {3 t; ~) D+ v- E% _
                          The initial process's (also known as the main thread's) stack.
0 W6 R3 Q9 {( t1 ?1 t: p, i
' o4 }3 S7 t! }/ a0 {5 j! Q2 r                   [stack:<tid>] (since Linux 3.4); Q+ r& T$ {7 l# ^+ J- Z( U
                          A thread's stack (where the <tid> is a thread ID).
) w$ V5 B. S5 P: ]* L6 K              It corresponds to the /proc/[pid]/task/[tid]/ path.
9 _8 N$ B4 n3 i( e1 a9 h. R9 `, E
                   [vdso] The virtual dynamically linked shared object.) z" I+ I: j/ c. J! V) W
7 j  N, \! L% R0 {2 |& e5 g
                   [heap] The process's heap.7 Q% R! N5 N/ `2 y2 @. s* _
) A! f9 S4 R+ L9 H
              If the pathname field is blank, this is an anonymous mapping as obtained via the mmap(2) function.
1 k" Q8 x; b% K# k8 w          There is no easy  way  to  coordinate. ]" H6 w4 u0 r0 l. u) F
              this back to a process's source, short of running it through gdb(1), strace(1), or similar.* Q2 o' g1 v8 a( J7 ?8 u, n2 E

1 m! Q' {' t* N; q2 t              Under Linux 2.0 there is no field giving pathname.' j! C0 q) @- Z; ~, E

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

#include <stdlib.h>2 ]6 q" v3 D2 @5 b2 I. X2 ~
#include <stdio.h>
0 V' h; q; x" Z#include <string.h>
' E, V! i; O; Z) d5 O$ f( y7 y#include <unistd.h>0 ^6 E1 w- K5 O1 x

. x. k8 n4 e2 w: }+ m4 P/**              
  r8 q/ [$ X" g * main - uses strdup to create a new string, loops forever-ever
- B) ~0 G6 i- [6 S; r* f$ c3 ^ *               
$ P* R* |3 n5 _2 `2 F) {0 S5 o* ? * Return: EXIT_FAILURE if malloc failed. Other never returns
% d% B# i( ^3 V* G8 Q) E* t */: @& O  B+ g. \7 |
int main(void)# z$ }. J6 w; b4 y, S
{" o: O* B  k/ w) e* Q" [  P6 o. r5 O
     char *s;
% S; o* I. o! e3 n8 }' N2 C     unsigned long int i;4 V( O( N' q$ k1 U% j  }' y
6 B4 D* j7 H* J5 ]! F+ z
     s = strdup("test_memory");: D5 E5 R7 l- n' M5 P/ `8 ~9 y
     if (s == NULL)
2 Y* g& k' q7 o" ^5 O     {
  a% h8 [, E7 ]. D- e( z" o0 E          fprintf(stderr, "Can't allocate mem with malloc\n");
1 B( w$ a  I( _  b+ w2 l          return (EXIT_FAILURE);6 y5 d3 m" G6 h6 g3 k) X8 \
     }$ {- |! o( x& @, Y8 h: C6 v  D
     i = 0;
; j: K! f3 g; w& C2 J' j     while (s)
" b$ C# E' }4 |6 g! p) h     {
  ?! k  q; u( s4 \- y; G$ }          printf("[%lu] %s (%p)\n", i, s, (void *)s);
! e  ^+ w) ?' N& l& t          sleep(1);$ ~9 D0 ~: b' ], M  H' A- W
          i++;
8 \+ q4 |, L/ r     }4 w8 D8 z  W8 L. \' ^/ S! k
     return (EXIT_SUCCESS);( S3 y& _7 j1 s2 ^5 C) V' G( l
}; x- i" ~/ ?! C* t
编译运行:gcc -Wall -Wextra -pedantic -Werror main.c -o loop; ./loop
9 B. n. Z: `7 r( z+ i输出:$ e* ]$ h1 M( x6 H$ R# z1 X
[0] test_memory (0x21dc010)& n# w; N" o) M' z) E9 |
[1] test_memory (0x21dc010)
  s8 C5 h8 G6 p7 I& ^& u% t[2] test_memory (0x21dc010)
2 S" F8 l% |, |7 c[3] test_memory (0x21dc010)! P3 `1 v- \8 e! K' }0 Q
[4] test_memory (0x21dc010)8 d; x, K" h3 |7 X$ T6 V7 Y
[5] test_memory (0x21dc010)
6 b! N, C; C: k8 O' m[6] test_memory (0x21dc010)
  K2 ?& a* M' ?8 M  i9 F...0 a5 |6 O4 D; D

: e! S! J) u- d+ T) D

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

. Z7 E8 }- R5 z6 z1 L/ ]3 A
首先找到进程的进程号

ps aux | grep ./loop | grep -v grep( g! c& @( _& h  I: \& I
zjucad    2542  0.0  0.0   4352   636 pts/3    S+   12:28   0:00 ./loop
# a" x+ M; M0 O. h+ n8 e. N$ w

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

00400000-00401000 r-xp 00000000 08:01 811716                             /home/zjucad/wangzhiqiang/loop. k  m! {/ q6 l" |' R
00600000-00601000 r--p 00000000 08:01 811716                             /home/zjucad/wangzhiqiang/loop( Z2 ~' _/ W9 ~1 [: Z
00601000-00602000 rw-p 00001000 08:01 811716                             /home/zjucad/wangzhiqiang/loop1 R4 m. O* z( Q( j$ @" K+ C% e, R0 x$ x
021dc000-021fd000 rw-p 00000000 00:00 0                                  [heap]
! K. {6 w% f2 V% @3 q5 B( h3 v7f2adae2a000-7f2adafea000 r-xp 00000000 08:01 8661324                    /lib/x86_64-linux-gnu/libc-2.23.so
) r$ i* n/ V, _$ x( F7 A0 T7f2adafea000-7f2adb1ea000 ---p 001c0000 08:01 8661324                    /lib/x86_64-linux-gnu/libc-2.23.so/ G7 ~6 [, R8 p: U7 s3 l, }
7f2adb1ea000-7f2adb1ee000 r--p 001c0000 08:01 8661324                    /lib/x86_64-linux-gnu/libc-2.23.so5 y9 ?8 H6 {3 Y* J
7f2adb1ee000-7f2adb1f0000 rw-p 001c4000 08:01 8661324                    /lib/x86_64-linux-gnu/libc-2.23.so
4 `$ d  v6 s" e7f2adb1f0000-7f2adb1f4000 rw-p 00000000 00:00 0
$ `* m1 i8 \9 x0 k* d7f2adb1f4000-7f2adb21a000 r-xp 00000000 08:01 8661310                    /lib/x86_64-linux-gnu/ld-2.23.so$ u7 h6 C( Y- _8 a8 ^
7f2adb3fa000-7f2adb3fd000 rw-p 00000000 00:00 0' J0 G+ o: z- u
7f2adb419000-7f2adb41a000 r--p 00025000 08:01 8661310                    /lib/x86_64-linux-gnu/ld-2.23.so
- o! u5 I; M# h$ {/ t2 |0 T7f2adb41a000-7f2adb41b000 rw-p 00026000 08:01 8661310                    /lib/x86_64-linux-gnu/ld-2.23.so9 i4 d' s% H7 j
7f2adb41b000-7f2adb41c000 rw-p 00000000 00:00 0( D2 {' H$ u: k3 N* }7 C
7ffd51bb3000-7ffd51bd4000 rw-p 00000000 00:00 0                          [stack]
4 j: U* R( i6 ?5 ^. y7 h7ffd51bdd000-7ffd51be0000 r--p 00000000 00:00 0                          [vvar]
. ~. |& Y3 F2 H' A! [7 T7ffd51be0000-7ffd51be2000 r-xp 00000000 00:00 0                          [vdso], O" l. }+ m; P" r3 l
ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
9 m, l+ ?6 p3 W4 o. r8 m. @

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

#!/usr/bin/env python3
8 k" O1 Y; S+ S! K* K; ?6 Q'''            
* Z& i  x. C. g9 xLocates and replaces the first occurrence of a string in the heap# C/ N% S- Q$ O+ q6 W4 D- I1 n
of a process    3 l8 C  V5 q/ P
. d* x- A7 o; n+ T3 D
Usage: ./read_write_heap.py PID search_string replace_by_string
* X2 h% I  i. F2 K$ fWhere:           5 H9 G) b4 N9 d/ Y, N8 M1 H
- PID is the pid of the target process
7 {5 T% x4 H0 ~7 Z; `1 A- F) I- search_string is the ASCII string you are looking to overwrite8 {% @  V1 k6 X5 ^/ J. J$ Y6 |  q+ R
- replace_by_string is the ASCII string you want to replace+ d% Z1 V, ^6 f8 Y& V8 }
  search_string with* x4 s4 A% q7 k; r; [
'''
# T) ^( L& I% A' g2 W% b) I* V, b
import sys% a  n: b* t, R8 @! N) w3 l
7 ~# G/ p5 [6 {& g' f5 ~0 q
def print_usage_and_exit():# t2 e3 A- R( y, Z& X  P) J
    print('Usage: {} pid search write'.format(sys.argv[0]))+ N3 D  e2 a7 u) e9 S
    sys.exit(1)( j1 f0 |, R- Q5 `' |
( Q* N. u7 F5 @4 v4 C
# check usage  
; W7 w3 d/ w* \. i" \; lif len(sys.argv) != 4:- N: g+ `9 U. ]/ d4 r2 F! ~6 T
    print_usage_and_exit()3 A  u/ L% B( M$ ?% Z. M3 A! B
) {" }0 }8 [6 M- t
# get the pid from args; M0 n4 `' T+ n2 J- i% o" m9 o0 X
pid = int(sys.argv[1]): Q: U5 m; v' H3 c; s' k. w; M
if pid <= 0:$ v6 S/ y) m+ N5 ~) K7 }/ W
    print_usage_and_exit(). f' [6 q  q: n3 o4 N& G- M& T
search_string = str(sys.argv[2])3 v3 g9 C  g2 m7 K
if search_string  == "":6 K. Y+ m  G$ d5 U. Y1 @( B. e
    print_usage_and_exit()$ y( h4 a8 ?6 r0 c1 J3 l/ c
write_string = str(sys.argv[3])
( R. ^& S$ Y0 n3 y5 S% j0 Z  Yif search_string  == "":
9 U* I7 B& ]2 G3 B* ?5 [! W2 ^    print_usage_and_exit()
! y/ w7 C, T! M, _5 E5 t: m2 {2 |- ~5 B) J- O% D: I
# open the maps and mem files of the process
* g7 `/ _& P5 A0 }3 e0 C8 n) m# nmaps_filename = "/proc/{}/maps".format(pid)9 s" |! K, T- t' b5 |5 M0 `
print("
  • maps: {}".format(maps_filename))
    ' b7 S% J" M) f& V5 Vmem_filename = "/proc/{}/mem".format(pid)# p4 l0 ?6 p: E' N% ]) t- w
    print("
  • mem: {}".format(mem_filename))
    : n0 f1 G9 a1 B- \% ^1 |) E( i7 c7 l5 ?% `% F- M( `  z: _4 t
    # try opening the maps file3 H, r: F; {9 A8 f% I
    try:
    . k" y, t  N" y7 w9 i! H; l" v    maps_file = open('/proc/{}/maps'.format(pid), 'r')
    . ?" B; H; Z- y4 a4 w- p: Jexcept IOError as e:
    1 m; [* j) D5 ~% C) p1 Q- ]    print("[ERROR] Can not open file {}:".format(maps_filename))3 z# S( S, s) k# w- W; r5 g
        print("        I/O error({}): {}".format(e.errno, e.strerror))
    ) }2 g% j+ h3 E, r    sys.exit(1)
    + Y5 `! k' T' |7 S* I
    3 x9 J3 I7 P7 |6 S4 v3 a* o! Afor line in maps_file:8 v1 Q$ b2 |$ h, u
        sline = line.split(' ')0 }! F0 T) D* X/ y- d
        # check if we found the heap
    1 H. j% X0 Y2 Q& a' l' o    if sline[-1][:-1] != "[heap]":! U) r* ^# m1 F& Z% B
            continue
    3 \8 g+ i! x- e7 i5 P    print("
  • Found [heap]:")
    ) t, \0 t/ o( b% I9 v" q: A2 F+ a
    - V3 Z( l& H5 J7 g+ m! \) T( z: F    # parse line+ [4 o" }, K- C  b; |
        addr = sline[0]* D; q- ?3 w4 [5 L# q
        perm = sline[1]
    1 W! M5 i& b2 ~( k* u, g: D4 E    offset = sline[2]
    8 K* q( [& ^0 ^    device = sline[3]' {# b. e+ _$ n, e! u# N2 Y
        inode = sline[4]
    " S% X0 r3 r/ R% Q, o9 q9 Y    pathname = sline[-1][:-1]: X6 N  w9 o- ^: F
        print("\tpathname = {}".format(pathname))3 `  k' n# E: Y5 ]! D, R1 N% Z
        print("\taddresses = {}".format(addr))) h3 }) g& r& @
        print("\tpermisions = {}".format(perm)): h" S6 K9 V( c0 ?: U- B. A
        print("\toffset = {}".format(offset)). @9 H0 o( T1 U4 Y8 k. h
        print("\tinode = {}".format(inode))
    9 s  I5 z  b, @/ h; b
    ; u" J6 W4 Q; ]; f- g1 z+ M    # check if there is read and write permission/ n5 ~% R9 h0 W6 T
        if perm[0] != 'r' or perm[1] != 'w':* u9 O" o0 N& ~4 B) t* _4 t
            print("
  • {} does not have read/write permission".format(pathname))
    + k+ I$ K. \+ W" k# r# l6 v  q        maps_file.close()
    $ ~. @/ r( U( _* ~/ z        exit(0)) \* Q* c* s$ i; x
      C' C9 R. M' f& T! H
        # get start and end of the heap in the virtual memory
    ; _  F% i, q) f+ }    addr = addr.split("-")
    5 d$ c, n1 H3 x- Z# A    if len(addr) != 2: # never trust anyone, not even your OS
    . E, [! z* g9 M' r5 B        print("
  • Wrong addr format")/ Z: Y6 b# C8 P
            maps_file.close()
    1 y2 c' y3 F4 U# ~        exit(1)
    3 r  B  Y( g* Q$ I0 S" n, r& i    addr_start = int(addr[0], 16)6 n! w: [8 X8 [6 L' @+ v3 k. Z
        addr_end = int(addr[1], 16)
    & {/ S0 ^) ?$ g& ~    print("\tAddr start [{:x}] | end [{:x}]".format(addr_start, addr_end))
    " ~) T4 f; i( l- H2 e; d7 `7 G: Z6 ~' f. g0 i! A: S' \
        # open and read mem, P5 r5 l+ G. O% C7 k4 j& |. g( L
        try:. n" t' G- X1 W8 _; H' e( D! p
            mem_file = open(mem_filename, 'rb+')
    " ~! j3 n' q# b: [* ^    except IOError as e:
    , G6 A9 v' x  I7 `, k; j0 Z        print("[ERROR] Can not open file {}:".format(mem_filename))
    2 j6 t) r! A5 ~' @4 x        print("        I/O error({}): {}".format(e.errno, e.strerror))
    # E* }4 n* Y9 y3 v4 @$ N- w8 p: F        maps_file.close()$ K) e8 |; k4 o( J+ ^3 g/ e# K
            exit(1)
    & V, s1 O' x& ^% E, N6 Q1 z
    & h6 {2 @# O& u    # read heap  
    0 h1 j* h- Z1 a    mem_file.seek(addr_start)
    ) H" P' L% l0 g5 _    heap = mem_file.read(addr_end - addr_start)
    + ~, [( e. h% _/ x9 e# Y
    - T8 D& [: [) k  [" r' ~    # find string
    1 N, p& d# y3 Q" O+ x- l9 u! n7 ^    try:
    ( f3 V6 ], K5 b! H        i = heap.index(bytes(search_string, "ASCII"))
    8 [- h/ W4 ~1 G$ S4 e/ ], O    except Exception:# Q/ }0 ~* t7 ~- r$ k: e5 e0 X
            print("Can't find '{}'".format(search_string))  _/ L8 U2 u  v$ ^+ m- z. c! J
            maps_file.close(); m) T+ x7 r3 O2 e/ m9 u$ W# Z# H/ i
            mem_file.close()' u) X1 K" a* h$ f( v1 {1 r
            exit(0)$ o$ O# G9 w( S+ U
        print("
  • Found '{}' at {:x}".format(search_string, i))' s8 l5 }8 M0 P4 y3 O- I

    , p3 U- L) Q( h7 j5 }. B: b% Q  C    # write the new string9 M3 D- M- Z6 [4 t0 r
        print("
  • Writing '{}' at {:x}".format(write_string, addr_start + i))
    6 p9 ~" ^& c0 s& `) x# y* G% [    mem_file.seek(addr_start + i)
    8 n5 _; G# e" y  |    mem_file.write(bytes(write_string, "ASCII"))1 k% l7 j! \5 j! Q! X! m
    3 f) v4 m- E: m  k! s+ \, m
        # close files6 A4 k' z- A* `
        maps_file.close()
    0 t% Q9 p$ p% \8 j/ j- J    mem_file.close()
    0 p% {0 r( R+ G# X
    ! ~9 Q! l/ D: V" o" x- a0 @    # there is only one heap in our example
    0 Y6 T& s2 Z  d6 b    break
    7 J! F0 Y1 x* J# w

    运行这个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]:
    8 ~- M/ c: o& ]. d# C        pathname = [heap]1 i) V. V. {' P0 W
            addresses = 021dc000-021fd000  w& C; |- D9 [% H# J! F/ }6 A2 J9 _
            permisions = rw-p
    7 |9 T* o6 A& S, i" Y9 K4 z        offset = 00000000
    9 o, w1 O3 i1 ?* f        inode = 0
    4 a4 N/ G' N; ]$ x$ \1 H( s        Addr start [21dc000] | end [21fd000]
  • Found 'test_memory' at 10
  • Writing 'test_hello' at 21dc010
    + {  Q4 w8 t- X/ h6 R

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

    [633] test_memory (0x21dc010)' }' a1 g( S/ X' _0 q* p  G. Z7 Z1 {
    [634] test_memory (0x21dc010): g; W! `1 ]* ~0 N5 T& x
    [635] test_memory (0x21dc010)* r1 [7 c0 `& \& |
    [636] test_memory (0x21dc010)
    0 M/ D9 L! H! m6 ~[637] test_memory (0x21dc010)
    : q5 p1 h7 G) o5 Z% G! d[638] test_memory (0x21dc010)
    8 K0 B9 ]- b9 h: ^; J[639] test_memory (0x21dc010)7 r; F' n3 @$ W. V
    [640] test_helloy (0x21dc010)
    ) G2 B1 ]2 |. i+ V( m# w: l3 O[641] test_helloy (0x21dc010)' B: ~# L3 j; m2 T9 Q
    [642] test_helloy (0x21dc010)8 U! E/ i6 I- d- X4 d, M) S% c2 ]
    [643] test_helloy (0x21dc010)+ Q5 J  U1 m  s
    [644] test_helloy (0x21dc010)
    - k1 a& m, y" D& _+ n6 H. r' |[645] test_helloy (0x21dc010)
    ) I% h: r# F0 F5 Y. V# V

    实验成功.

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

    再列出内存空间分布图
    7 l0 \- u! ]* m* A) Q# c
    微信图片_20201119201739.png

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

    堆栈空间

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

    #include <stdlib.h>
    + U+ y/ H3 Z1 D2 J% g+ A3 U#include <stdio.h>
    " k8 M2 B8 N, p, j; C; h# y; k#include <string.h>
    3 e  N% f4 k9 X* Y
    * ^) @/ D# [' y  P/**
    6 X* ~. _  q! E5 s! Y1 ^3 Q; O: \ * main - print locations of various elements
    % C2 W+ O1 _$ e4 ^$ I5 f  ~ *  J) |$ u4 q  d# B' t5 G- l
    * Return: EXIT_FAILURE if something failed. Otherwise EXIT_SUCCESS
    ; i  G, Z6 W; x9 b; \ */
    / X; I. j0 _1 ~% `, mint main(void)
    3 N$ e8 x& _/ }( y{
    ' E6 N6 }' ~( u! Z  M$ N    int a;
    8 H. A! e6 `, A0 B    void *p;
    6 i8 f% c8 G" I, y2 W8 i) T( d" F" H6 g
        printf("Address of a: %p\n", (void *)&a);% d% w" [, |- O
        p = malloc(98);( Z: Q6 {( e" ?
        if (p == NULL)
    + W% e; r( N0 F- k% }* C/ w    {
    9 @0 t, j; R- w/ {        fprintf(stderr, "Can't malloc\n");: J! y# [  G* L
            return (EXIT_FAILURE);
    ) f2 h0 @+ O8 a6 a( y% H" M, y    }
    . {0 a: c/ R. \5 W! {) x1 U* E    printf("Allocated space in the heap: %p\n", p);
    9 y; I) H/ Y+ R; t+ s* b3 U    return (EXIT_SUCCESS);
    - A( e! W; ?2 [: v7 b}' x' Z( y/ b8 Y4 S' m
    编译运行:gcc -Wall -Wextra -pedantic -Werror main.c -o test; ./test
    / Q% }% L* T6 H# t( s输出:
    , c9 _7 r+ O/ s% G* tAddress of a: 0x7ffedde9c7fc
    ' y8 ~- x; O* ^% i9 hAllocated space in the heap: 0x55ca5b360670
    ) M" D$ o: c$ s, e

    通过结果可以看出堆地址空间在栈地址空间下面,整理如图:
    ) A2 J" h0 B0 B# _; [2 @5 k* s
    微信图片_20201119201742.png

    可执行程序

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

    #include <stdlib.h>  Y" i1 Z1 }( M" O
    #include <stdio.h>; `0 Z# {" }' P# E# G0 o7 n" [4 w
    #include <string.h>( z% {! V8 A3 b, @
    $ i( j' O/ g$ ^" U
    /**
    2 s. e' p7 T7 U8 _" s" M * main - print locations of various elements+ D, k  r$ W% F3 A. h% D& @
    *9 T$ d& D4 i6 l2 j& M
    * Return: EXIT_FAILURE if something failed. Otherwise EXIT_SUCCESS  I# Z2 X. @0 b; k% R  D
    */! X# X0 i3 o: d5 l) A- A' e
    int main(void)8 [- H# I) ]7 h
    {
    ; @9 x9 c+ ~6 z0 l    int a;
    * \( G) X. L( y8 v    void *p;
    5 ]; `4 d7 I/ o3 {9 O" E4 l* F
    - E: k, p2 {" ~% Z    printf("Address of a: %p\n", (void *)&a);
    # j/ c; `9 D3 s' q    p = malloc(98);' S. c4 `9 k. H9 O) H
        if (p == NULL)
    ) _) U5 i% @  ]6 v) M    {
    4 k! k5 A; m' K# }5 [* `        fprintf(stderr, "Can't malloc\n");6 i/ I' G8 g+ W
            return (EXIT_FAILURE);8 W2 a) F* |& f
        }/ P( x) y; I3 U# T/ F
        printf("Allocated space in the heap: %p\n", p);
    2 k. r- o. I+ Q' E0 V& S) g    printf("Address of function main: %p\n", (void *)main);2 {& d" c% m8 e" T% ^2 P2 G
        return (EXIT_SUCCESS);
    " Y$ g$ ~' s2 K( q}5 f9 `1 U0 w- }# a9 }
    编译运行:gcc main.c -o test; ./test
    5 E- y  }' T3 _9 u6 V输出:
    : q) p& c7 ]4 @0 f5 J' C! y  xAddress of a: 0x7ffed846de2c
    ( U/ J* Q; M5 v3 {7 q5 ?. yAllocated space in the heap: 0x561b9ee8c670
    - b# B# z; a7 s0 B) f, O! hAddress of function main: 0x561b9deb378a
    1 x* y- W' ^8 @0 E* ^) l

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

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

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

    • 第一个参数(argc): 命令行参数的个数
    • 第二个参数(argv): 指向命令行参数数组的指针
    • 第三个参数(env): 指向环境变量数组的指针
      ! N0 A% B8 e4 ~0 S) `

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

    #include <stdlib.h>0 a0 l8 h# A  ~! Q2 b, H; y# _- A
    #include <stdio.h>
    8 v8 u4 E2 [4 f6 k2 P4 Q#include <string.h>
    ) X' j' R  k8 x' `* Z
    6 q) \9 T% ]$ p& f) M/**
    & ?1 x1 Q! R6 {( L * main - print locations of various elements' K9 I# b- O; k" f
    *6 b% e9 F, K; d; V
    * Return: EXIT_FAILURE if something failed. Otherwise EXIT_SUCCESS" z% c% {% Z, Z1 R6 C" {
    */. J1 g, e( Y% N/ f/ N
    int main(int ac, char **av, char **env)
    6 t7 d# a$ `& f7 ?  i. x. y6 ]8 x6 t{
    4 c% P1 L# T, C# T4 @, ^        int a;
    + \) c% ^3 K% f! }  _6 p        void *p;3 v7 J- I+ s% @. h7 O1 S
            int i;
    , o/ c. s/ x5 Y$ ^" X% k
      |) Q! z8 a* F' s        printf("Address of a: %p\n", (void *)&a);2 z  @$ A( T- b$ M" {7 x
            p = malloc(98);
    $ R$ K5 R1 i' D) u7 q; Z" f, D# {$ r        if (p == NULL)
    . s9 n5 Z8 E3 X* V" m        {. d. v# U$ d! q) {; T
                    fprintf(stderr, "Can't malloc\n");9 k# i$ N6 f. y7 w% D
                    return (EXIT_FAILURE);( |6 S  I' l- u9 I
            }  ~0 M* o3 z5 I3 r! ], N& \/ O
            printf("Allocated space in the heap: %p\n", p);! q& w# r4 M. O- E% w9 \" s
            printf("Address of function main: %p\n", (void *)main);
    ) r4 W& P* A# E7 S. ?, E3 W        printf("First bytes of the main function:\n\t");% Y: A! F$ c2 }7 R" z$ H
            for (i = 0; i < 15; i++)
    ( B) i  F0 c: i  d  t  m. v        {, ]) O& |5 C! K' z
                    printf("%02x ", ((unsigned char *)main));1 u* }! {! Q0 v* `' g$ _
            }4 j7 F. u! {# j
            printf("\n");- x' I4 J2 \7 E) A' k( g8 M
            printf("Address of the array of arguments: %p\n", (void *)av);) l% R# R' C' O3 _) R3 K
            printf("Addresses of the arguments:\n\t");8 \+ h0 }+ x/ C" Z) \- |
            for (i = 0; i < ac; i++)( p! Z: [9 n* [: o' H2 _
            {" p1 ^+ t/ h9 G* n
                    printf("[%s]:%p ", av, av);8 T) u/ i7 t3 W# H) p
            }
    & `! D, @$ X6 t; w7 n        printf("\n");; Y, e4 ]& n+ p7 A  l
            printf("Address of the array of environment variables: %p\n", (void *)env);0 J4 Y( }  ]5 v& M- U& g2 _
        printf("Address of the first environment variable: %p\n", (void *)(env[0]));
    ( ?3 P8 H1 J8 k8 c' F' y        return (EXIT_SUCCESS);% Q8 {) L6 ^4 E/ \4 |: m5 j1 O6 J
    }
    1 W, }1 Z: j  r7 @编译运行:gcc main.c -o test; ./test nihao hello8 g% V' p% J7 {0 P& \/ ~$ f! p4 O
    输出:
    % k* F. n9 x" x  ?8 e  GAddress of a: 0x7ffcc154a748% t& u0 P7 Z- a
    Allocated space in the heap: 0x559bd1bee6702 D4 r0 x7 L( i
    Address of function main: 0x559bd09807ca
    $ Z1 C! V+ Q- R2 F+ Z$ iFirst bytes of the main function:
    1 s: M( |& H; v4 k1 [. q) G0 |        55 48 89 e5 48 83 ec 40 89 7d dc 48 89 75 d0
    7 v6 I( h* F' }Address of the array of arguments: 0x7ffcc154a848
    5 D0 o4 Q% H! ^0 q6 x" `Addresses of the arguments:
    ) ?+ _; o5 O7 H. I% Y/ O5 ^6 d# o        [./test]:0x7ffcc154b94f [nihao]:0x7ffcc154b956 [hello]:0x7ffcc154b95c
    / c" A; E7 n+ q7 UAddress of the array of environment variables: 0x7ffcc154a868# D# l7 C1 w9 @2 J3 y3 ]& G
    Address of the first environment variable: 0x7ffcc154b962
    7 O1 V) n9 {  R1 _. f1 S8 M

    结果如下:
    / d0 V9 _& P9 y" _% g+ }main(0x559bd09807ca) < heap(0x559bd1bee670) < stack(0x7ffcc154a748) < argv(0x7ffcc154a848) < env(0x7ffcc154a868) < arguments(0x7ffcc154b94f->0x7ffcc154b95c + 6)(6为hello+1('\0')) < env first(0x7ffcc154b962)+ x' I1 _! |% f" k$ N" L
    可以看出所有的命令行参数都是相邻的,并且紧接着就是环境变量.

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

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

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

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

    #include <stdlib.h>
    6 y, v$ _" u  G4 p5 F" s#include <stdio.h>
    0 _# U+ [' R5 H( g* x#include <string.h>8 f5 k* b" J( q' `

    ! \9 h  ^/ L* R" w9 C/**                                                                                                      9 K- N+ f1 [$ z" ]
    * main - print locations of various elements                                                            6 r/ A9 p* h) W3 P
    *                                                                                                      
    - }+ I, |& g$ O$ m * Return: EXIT_FAILURE if something failed. Otherwise EXIT_SUCCESS                                      5 {) I" G1 P9 t* \8 M
    */% m- Y: h7 d! ~( C8 y
    int main(int ac, char **av, char **env)
    ' Y' u2 f! {8 }% L1 ^& }/ |# c{
      H  Y+ L! @5 ]' x7 P     int a;" @9 _' A" M- w. E5 f1 n- y7 E/ D
         void *p;
      {. k. Q& B* I5 L: L+ [     int i;5 z$ _: M0 `) j9 p/ m
         int size;* l; ]  J9 @6 `' b$ o- \- Z% w

    ! I+ O; \: ~4 h8 Z2 B+ |. q     printf("Address of a: %p\n", (void *)&a);
    : F4 s4 N; o& S, G  i     p = malloc(98);
    7 e8 B5 _( u1 B     if (p == NULL)
    ! ?- J4 I3 b" x# Y     {# l9 z) i7 _7 l  N; i* @" t
              fprintf(stderr, "Can't malloc\n");8 h! a  @: ^3 ~: Z( r. @# p
              return (EXIT_FAILURE);
    " `4 F/ U( K8 w     }+ {* C+ G# @. ~7 v1 `1 J
         printf("Allocated space in the heap: %p\n", p);
    9 z+ G/ {2 _' d* P' c' t9 {1 p     printf("Address of function main: %p\n", (void *)main);- Z: |2 t' G7 r0 P4 I
         printf("First bytes of the main function:\n\t");4 ?3 o9 k% [) k! s  g. Q0 g1 N
         for (i = 0; i < 15; i++)
    6 c5 z& B( ?9 D/ o5 F$ A     {" E% q7 m! c) M& Y  s1 F$ h
              printf("%02x ", ((unsigned char *)main));
    ' @- z. x4 Q4 E/ p2 ^  O     }" e# M1 J' \  ^" g1 _
         printf("\n");
    4 F' j# O8 j. R' x& e     printf("Address of the array of arguments: %p\n", (void *)av);) G3 a# `/ g! L- \0 b0 ]
         printf("Addresses of the arguments:\n\t");4 z' r* i! F6 l: m. w8 I  t
         for (i = 0; i < ac; i++); n* F, x; N" ]; ]' Z% C4 @4 Z9 ~
         {: R0 i9 {4 h# k4 h
              printf("[%s]:%p ", av, av);
    1 r3 H( t9 k% D! [     }8 S" z7 ^8 e2 n
         printf("\n");
    1 ^1 {6 a, i& T! m9 Z1 D' @7 ^     printf("Address of the array of environment variables: %p\n", (void *)env);; x. ~7 A# x2 |2 l
         printf("Address of the first environment variables:\n");
    + y. ]8 `! K+ S0 G$ A/ ]7 Y     for (i = 0; i < 3; i++)
    % M" C/ X% @' m  {" q6 l* Y/ L' `5 Y     {# h; z$ X* v. i, A- Q
              printf("\t[%p]:\"%s\"\n", env, env);
    6 x7 ^8 H9 w- _0 Z! ?0 h; r8 n; z     }
    / Z9 Z8 `$ u) R/ V# F3 ^  S3 S3 i/ }     /* size of the env array */" b6 f8 U$ l: Z! D: a
         i = 0;! n  [3 \  L3 G. ~$ e
         while (env != NULL)9 n$ n# m2 s9 d0 o* L5 a, h8 m
         {% {" M* M' N2 j* ]
              i++;6 [" q( d+ c/ R+ L& _3 \3 t* e
         }
    3 O8 a4 l& C, n; H/ m     i++; /* the NULL pointer *// u! n0 ?9 }) m7 b8 q* ?: D
         size = i * sizeof(char *);- L" |! R1 j, O# I! s7 t
         printf("Size of the array env: %d elements -> %d bytes (0x%x)\n", i, size, size);, F+ m* J, s9 J* b( b9 t( c
         return (EXIT_SUCCESS);
    5 N# h4 Q/ ~: `3 c}
    ! B- `" _3 z2 f1 [, I& ]
    , ^& A! E! A% c5 c2 d9 d6 M) n) {编译运行:gcc main.c -o test; ./test nihao hello
    , @. _8 \" N" B输出:, J& C" [6 J# u6 \8 s
    Address of a: 0x7ffd5ebadff4; N  f7 G3 O9 ~" |7 x0 H
    Allocated space in the heap: 0x562ba4e13670
    # ~% M% g( R) c  J: F; _Address of function main: 0x562ba2f1881a
    , I( T4 l/ f5 H3 ?! j0 MFirst bytes of the main function:
    5 u+ v* Y2 h$ L4 y- F: t( E- U1 h        55 48 89 e5 48 83 ec 40 89 7d dc 48 89 75 d0
    1 @' f2 B' g0 M: k; l3 y/ JAddress of the array of arguments: 0x7ffd5ebae0f8
    , n# z" H6 ~+ F9 Y0 ]Addresses of the arguments:' q/ I% @4 {* W* P% I. t9 {7 K* h
            [./test]:0x7ffd5ebae94f [nihao]:0x7ffd5ebae956 [hello]:0x7ffd5ebae95c
    " u6 L& J9 _1 L( D% I6 bAddress of the array of environment variables: 0x7ffd5ebae118- @5 ^- ~$ E# E7 @- \
    Address of the first environment variables:
    8 e/ z/ a* s; l7 W. l4 Z: n        [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:"
    4 j# \) T: C$ R0 `, G        [0x7ffd5ebaef4e]:"HOSTNAME=3e8650948c0c"
    " |1 B9 }0 `# _/ @6 h# x9 y        [0x7ffd5ebaef64]:"OLDPWD=/"
    . Y2 F5 E) J: F  k) V8 G9 @# x; PSize of the array env: 11 elements -> 88 bytes (0x58)
    % {3 C9 K% T" e' H; V3 @: f
    ' @1 R9 ]2 h* ^5 a3 `运算结果如下:
    5 ~% w4 u) g7 P$ X5 Jroot@3e8650948c0c:/ubuntu# bc
    " B+ d# Q2 v- \' lbc 1.07.1/ w# P3 g  o; e8 {: V5 g, X
    Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006, 2008, 2012-2017 Free Software Foundation, Inc.
    : C" i% O9 E% s; G! }% J6 F& K% OThis is free software with ABSOLUTELY NO WARRANTY.& _: X3 v5 o$ q7 `" R6 u( T* s, a
    For details type `warranty'.
    ) e$ z  L$ w6 I/ a* Wobase=164 E$ P2 P$ x) q$ g% Y' ]
    ibase=16
    9 z# Q7 N2 z8 z! y0 c) B58+7ffd5ebae118. `2 T' o! l6 U& ]: O
    (standard_in) 3: syntax error7 O2 W5 ~# `# q6 g8 r) k- `2 o1 H2 M1 P
    58+7FFD5EBAE118
      e: P6 q( `1 y/ w& e) Y. I7FFD5EBAE1709 C- g0 P* M' K' a+ L* i
    quit
    9 W0 j1 E5 n% t! U: [/ e$ ~/ s0 i

    通过结果可知7FFD5EBAE170 != 0x7ffd5ebae94f,所以命令行参数地址不是紧随环境变量地址之后。


    % {, Z, u1 J" ]4 c0 z/ D0 ~8 @截至目前画出图表如下:' G5 e: i0 j3 S" g& U
    微信图片_20201119201749.png
    栈内存真的向下增长吗

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

    #include <stdlib.h>- e# m  A$ \" f; N+ y4 f) _4 L
    #include <stdio.h>' E/ f$ v! ^" _& K" d
    #include <string.h>
    ) a, ?2 t" t7 U; K/ A& h
    - K1 ]( l3 |/ k2 `0 n. A  N3 Avoid f(void)
    & P: I3 Z) l" G# A8 E& p- s6 d{" W! R0 z2 M6 ?5 o2 y
         int a;5 q0 w5 u' p" Y. A  @
         int b;( U' [/ a* C& A" ^& U2 T2 o( \2 |8 G
         int c;
    ; w$ g6 ]* n6 q3 K- p/ |, d+ V9 a& m& }5 c4 g  M
         a = 98;
    " h7 V0 F, S- \% f* H4 O     b = 1024;
    5 f4 |0 o  \( b( B' u; q/ Z     c = a * b;
    ( J9 M) L* g" d( j0 }/ a     printf("[f] a = %d, b = %d, c = a * b = %d\n", a, b, c);8 y5 l8 ?7 y  F7 Q
         printf("[f] Adresses of a: %p, b = %p, c = %p\n", (void *)&a, (void *)&b, (void *)&c);  {; Q( y" [6 ^
    }1 H& D, j# W1 m  X; k( j
    : S% v! b& s; r- L& z) s- H
    int main(int ac, char **av, char **env)$ _# F& i& a; ^& T6 q- S! K
    {+ v; k# N8 |2 X8 ]7 M) p) s/ [# Q
         int a;+ O/ h  W. R* a: P  x) i5 m
         void *p;  ?0 z; K; n% l  D1 l2 a
         int i;
    / f/ H  F( Y1 O% x0 r: A& p     int size;
    # `2 N. N" x& e& f2 Y5 w3 q8 v$ x
         printf("Address of a: %p\n", (void *)&a);
    ! h! A- P3 D- N' p9 I+ D3 w     p = malloc(98);
    1 c. Z$ l! s2 p% F+ N6 @& l5 ^; K     if (p == NULL)1 s5 Z: Z% P7 P+ u/ Z7 _
         {( V% a* |5 n3 `( O  z! \: U
              fprintf(stderr, "Can't malloc\n");
    * ^/ f2 |5 n( J5 @9 H) P          return (EXIT_FAILURE);
    9 V1 Q; m* C/ `0 u4 M# r5 d8 t. [3 |     }8 E% q) X2 J; j
         printf("Allocated space in the heap: %p\n", p);
    # E$ x2 O, a' Q+ k6 e1 x' d     printf("Address of function main: %p\n", (void *)main);
    1 [' G# _1 |( f     f();8 q3 H# G* }% j- g6 [' z
         return (EXIT_SUCCESS);
    " f2 D7 v- d9 t9 c}  G) g: S. `% s. N: |; S, E
    编译运行:gcc main.c -o test; ./test9 D0 ?/ s% `% l6 T$ [# n# _3 \
    输出:
    ' D/ M1 A5 j0 B1 H! \Address of a: 0x7ffefc75083c
    * U0 o- p4 M4 w# ?( ?Allocated space in the heap: 0x564d46318670
    $ \3 N  e/ ~/ g, I8 w4 ]6 YAddress of function main: 0x564d45b9880e
    8 c3 R" b! M% q# a1 b9 o. O/ n[f] a = 98, b = 1024, c = a * b = 100352
    6 u/ s! p7 k0 W0 T0 b6 B[f] Adresses of a: 0x7ffefc7507ec, b = 0x7ffefc7507f0, c = 0x7ffefc7507f4
    : v7 r' j; _5 g3 ~- R/ s$ E2 @0 D$ I

    结果可知: f{a} 0x7ffefc7507ec < main{a} 0x7ffefc75083c
    & F, I' s: X. ~可画图如下:$ r; C+ I6 e, Z( m9 A/ ~0 z
    微信图片_20201119201752.png

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

    堆内存(malloc)malloc

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


    ; w; k" n$ B# {+ M0 o; Z2 a$ uman malloc:

    [...] allocate dynamic memory[...]- m: X# p4 G- V2 t. j" _8 w
    void *malloc(size_t size);9 E* z% `. @$ Z
    [...]  M; f. {7 z9 ?! E
    The malloc() function allocates size bytes and returns a pointer to the allocated memory.* x5 f0 |' N! q
    不调用malloc,就不会有堆空间[heap]

    看一段不调用malloc的代码

    #include <stdlib.h>
    * @/ G0 U9 U  @! L6 Z#include <stdio.h>
    & {# }  p5 U6 [7 G& t! ?4 m* {8 J6 \) z& U( m  G9 f3 Q
    /**
    - \* h/ W1 c2 D, v0 I$ H. _ * main - do nothing, r- |: A$ L$ C( V# e5 r" U) m
    *9 z0 Q6 E" @! u2 e7 u2 f5 i
    * Return: EXIT_FAILURE if something failed. Otherwise EXIT_SUCCESS, J  L5 o( R; `6 y) g: O5 K' [1 \
    */6 ]8 r5 \$ w0 m" h3 |. b" g
    int main(void)# m* V+ y4 g0 O7 B
    {# k% N9 j6 ^3 ]& v( c9 M4 z
        getchar();
    $ ~+ O  b0 |- ^6 q: f    return (EXIT_SUCCESS);' N  i( E+ c! O
    }& K/ ?! R2 x* g; y. K. j# n" L! e
    编译运行:gcc test.c -o 2; ./2+ d$ E1 y1 v  n2 F( \4 s
    step 1 : ps aux | grep \ \./2$6 j* S- x7 |; J6 T+ V
    输出:/ D! M  e# a# v6 f4 b
    zjucad    3023  0.0  0.0   4352   788 pts/3    S+   13:58   0:00 ./2' F* I  ~. U. C# {: |( X/ P+ J
    step 2 : /proc/3023/maps; H5 L0 x  N2 t( r  k: Z
    输出:: M2 l7 B  ~; J! k$ W% G( ?3 d
    00400000-00401000 r-xp 00000000 08:01 811723                             /home/zjucad/wangzhiqiang/2  y$ W- k3 s2 ]7 B1 ~. [
    00600000-00601000 r--p 00000000 08:01 811723                             /home/zjucad/wangzhiqiang/2
    / n1 i1 _% E, `5 h, s9 ]8 [00601000-00602000 rw-p 00001000 08:01 811723                             /home/zjucad/wangzhiqiang/24 a7 N$ F% {1 v& y" N, \$ e. h* B
    007a4000-007c5000 rw-p 00000000 00:00 0                                  [heap]
    ( H/ _4 i; R# ~+ q, Z7f954ca02000-7f954cbc2000 r-xp 00000000 08:01 8661324                    /lib/x86_64-linux-gnu/libc-2.23.so0 l% T5 B: z5 r; I
    7f954cbc2000-7f954cdc2000 ---p 001c0000 08:01 8661324                    /lib/x86_64-linux-gnu/libc-2.23.so
    3 j2 l$ B0 \2 _/ x2 w7f954cdc2000-7f954cdc6000 r--p 001c0000 08:01 8661324                    /lib/x86_64-linux-gnu/libc-2.23.so6 S6 Y. e8 S3 Q4 k) Q5 A/ i8 v( S
    7f954cdc6000-7f954cdc8000 rw-p 001c4000 08:01 8661324                    /lib/x86_64-linux-gnu/libc-2.23.so
    ) r/ \" _$ `! Q. m9 ]7f954cdc8000-7f954cdcc000 rw-p 00000000 00:00 05 @; d5 {4 M1 f  B1 p0 {
    7f954cdcc000-7f954cdf2000 r-xp 00000000 08:01 8661310                    /lib/x86_64-linux-gnu/ld-2.23.so
    1 p8 s, y8 b" V# d. j0 v+ p7f954cfd2000-7f954cfd5000 rw-p 00000000 00:00 0
    / z( v6 u/ G7 W5 }' \% h7f954cff1000-7f954cff2000 r--p 00025000 08:01 8661310                    /lib/x86_64-linux-gnu/ld-2.23.so$ U/ K) i1 _3 u( }! J) q) L
    7f954cff2000-7f954cff3000 rw-p 00026000 08:01 8661310                    /lib/x86_64-linux-gnu/ld-2.23.so
    - ~8 M" y3 }. B7f954cff3000-7f954cff4000 rw-p 00000000 00:00 09 A' C) @2 h2 _: R' T
    7ffed68a1000-7ffed68c2000 rw-p 00000000 00:00 0                          [stack]
    3 Y  A* |7 D* `) }1 r  ]" K7ffed690e000-7ffed6911000 r--p 00000000 00:00 0                          [vvar]
    / e8 s6 Z* s* W& f7ffed6911000-7ffed6913000 r-xp 00000000 00:00 0                          [vdso]- ~! g2 v8 ~2 T
    ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]
    # W; c/ H: E; \, R  n) @) i4 W

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

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

    #include <stdio.h>
    6 J8 O, H% @( Z( K5 w  j#include <stdlib.h>+ _" D  ~1 `7 h: M
    8 }- M: A2 F: U! _# o
    /*** a# E& S6 g1 |' c3 `5 o+ A
    * main - prints the malloc returned address
    + V5 z/ u' N4 O' D *
    . j& D7 n. q# Y * Return: EXIT_FAILURE if something failed. Otherwise EXIT_SUCCESS
    8 R( R9 I! C& Z& v3 s */3 e6 |4 |" Q: m) J9 B* m. A
    int main(void)
    6 b; J" b8 X% @{
    : a: C1 @9 C, w' m  U, _    void *p;6 x: b3 X& ]: a3 T) m/ v1 Y+ W

    ) Z- A: R$ ?$ _$ Q6 P. q# {8 [    p = malloc(1);( ?% ~* }+ T$ c9 `: f
        printf("%p\n", p);
    : e3 {) J# V9 R2 g) e+ z, k. m    getchar();
    : V; r! n7 p. d; D/ o    return (EXIT_SUCCESS);4 v9 g4 f) ]- \' k& S+ Z$ A
    }
    , h7 u$ p- v- G' a( U: E编译运行:gcc test.c -o 3; ./3) g7 B$ Z0 r3 @2 _' m& j( A) g
    输出:0xcc7010
    ! q8 Y' U- F$ y8 S, Y5 t  S验证步骤及输出:$ o( ]2 j, Y' p2 V
    zjucad@zjucad-ONDA-H110-MINI-V3-01:~/wangzhiqiang$ ps aux | grep \ \./3$; T' N3 @/ G* p. m+ q' @
    zjucad    3113  0.0  0.0   4352   644 pts/3    S+   14:06   0:00 ./3
    . w" x$ ~) l$ q" L& x! _( O  mzjucad@zjucad-ONDA-H110-MINI-V3-01:~/wangzhiqiang$ cat /proc/3113/maps
    3 c9 a2 G9 F& S0 e7 y00400000-00401000 r-xp 00000000 08:01 811726                             /home/zjucad/wangzhiqiang/3
    9 G  I- G# C: j! J% k( n00600000-00601000 r--p 00000000 08:01 811726                             /home/zjucad/wangzhiqiang/3& ]% g( R2 y, v/ Q8 P
    00601000-00602000 rw-p 00001000 08:01 811726                             /home/zjucad/wangzhiqiang/3
    / G6 S4 m- @& Y% V00cc7000-00ce8000 rw-p 00000000 00:00 0                                  [heap]- F. _6 r+ C& }4 F: u0 [
    7fc7e9128000-7fc7e92e8000 r-xp 00000000 08:01 8661324                    /lib/x86_64-linux-gnu/libc-2.23.so
    + G& f+ K2 ]% o! A: ]7fc7e92e8000-7fc7e94e8000 ---p 001c0000 08:01 8661324                    /lib/x86_64-linux-gnu/libc-2.23.so3 O5 p+ D6 T7 R; w; h2 o
    7fc7e94e8000-7fc7e94ec000 r--p 001c0000 08:01 8661324                    /lib/x86_64-linux-gnu/libc-2.23.so9 i3 w& U( }4 t$ `; p& Z* ]
    7fc7e94ec000-7fc7e94ee000 rw-p 001c4000 08:01 8661324                    /lib/x86_64-linux-gnu/libc-2.23.so/ g. i0 Y' T/ S1 x# u/ d5 M
    7fc7e94ee000-7fc7e94f2000 rw-p 00000000 00:00 0
    4 ?. U" Q) b9 Q$ t: O0 g- K7fc7e94f2000-7fc7e9518000 r-xp 00000000 08:01 8661310                    /lib/x86_64-linux-gnu/ld-2.23.so
    1 N, W. E# X  q2 `3 H0 v7fc7e96f8000-7fc7e96fb000 rw-p 00000000 00:00 0
    * G, U: F% u, s, x/ ]: O7fc7e9717000-7fc7e9718000 r--p 00025000 08:01 8661310                    /lib/x86_64-linux-gnu/ld-2.23.so( R5 u( }* c7 T1 e. m
    7fc7e9718000-7fc7e9719000 rw-p 00026000 08:01 8661310                    /lib/x86_64-linux-gnu/ld-2.23.so
    % I5 g% K+ {5 t4 S7fc7e9719000-7fc7e971a000 rw-p 00000000 00:00 0- Q3 G/ c$ L: Q) e/ q. U3 \  v
    7ffc91c18000-7ffc91c39000 rw-p 00000000 00:00 0                          [stack]
    , u9 w0 z5 r" ^( C7ffc91d5f000-7ffc91d62000 r--p 00000000 00:00 0                          [vvar]
    : V4 L" v* J- T  R0 S7ffc91d62000-7ffc91d64000 r-xp 00000000 00:00 0                          [vdso]
    8 h" j/ O- `3 X# j% f1 ]ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]& D/ H1 U" x4 P3 d+ c1 h+ l- \

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

    strace, brk, sbrk

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

    #include <stdio.h>4 r6 b; |. B) ~9 d+ K4 t: y! a
    #include <stdlib.h>
    & ?$ g- z  e+ ~3 ~" B, Y/ Y#include <unistd.h>. \; }' S0 I" b

    0 A. y+ `, _; `3 L2 o9 U" u' `/**3 x# B) }; T3 g' o/ P3 n4 R% S3 T
    * main - let's find out which syscall malloc is using) ]3 L0 r, J0 c3 f
    *
    ' ], j: x0 m: a! B6 T) R * Return: EXIT_FAILURE if something failed. Otherwise EXIT_SUCCESS$ U% m: k* J0 W( g. A' H
    */2 d; n+ O  |2 L. e& e+ g. Y
    int main(void)6 S% F1 \- x% K/ s) w7 z
    {
    6 x4 L' l: g& d    void *p;
    9 k; Z3 C% i/ f. n! S3 {8 H) i* V4 v' C! Q" h8 e1 C8 q) H
        write(1, "BEFORE MALLOC\n", 14);( @6 S' ?2 B; w% K! _( p' C
        p = malloc(1);
    2 V" P( B* A) _( N- ]0 b, N3 P    write(1, "AFTER MALLOC\n", 13);
    - ]- Z7 E. Z7 W& h% ?& {# R    printf("%p\n", p);6 f. k+ }* n/ v' Z! Z2 t
        getchar();
    ) c1 _0 t& E) Q    return (EXIT_SUCCESS);
    ' |8 p; L8 U# A- [# G4 y}
    / }# Z4 U  H# b编译运行:gcc test.c -o 4( r- q4 [" C( q4 \( |+ S
    zjucad@zjucad-ONDA-H110-MINI-V3-01:~/wangzhiqiang$ strace ./4
    , k% D/ G2 X+ H2 {execve("./4", ["./4"], [/* 34 vars */]) = 0
    % Q  N3 M. ?  j; w. K9 Sbrk(NULL)                               = 0x7810007 k# s8 ?' e  I& w0 @' R6 g# O7 {
    access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
    7 O' K$ V4 ]3 k2 l! O$ W. Haccess("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
    2 @9 d/ N5 q, r; q$ }' y+ }. topen("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3# f. i& e8 E* `' x9 @/ }2 R
    fstat(3, {st_mode=S_IFREG|0644, st_size=111450, ...}) = 0
    / y& y" v2 f0 y8 D" Z0 D$ qmmap(NULL, 111450, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f37720fa000' G, P0 T6 y! n4 k3 Z; g
    close(3)                                = 0
    1 R7 M' H( J4 @8 Uaccess("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
    # @1 u9 ]4 A* `) o, p2 B( C! |- copen("/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
    6 E. v+ q, \, i1 Y) 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
    1 V  C- ~7 W1 c9 N8 a# mfstat(3, {st_mode=S_IFREG|0755, st_size=1868984, ...}) = 0" H7 f" a) E- h) v
    mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f37720f90006 h% V- z8 E. J9 l4 K
    mmap(NULL, 3971488, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f3771b270001 S. v1 N* {0 ?7 J% U9 q
    mprotect(0x7f3771ce7000, 2097152, PROT_NONE) = 0
    & W) ^% g, G! s1 Y: C1 Ummap(0x7f3771ee7000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c0000) = 0x7f3771ee7000
    & a& @  b! [7 ?mmap(0x7f3771eed000, 14752, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f3771eed000
    3 N# z  [& w0 F6 jclose(3)                                = 0
      G. V1 L2 Q5 Xmmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f37720f8000
      E. H. p8 r. ^mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f37720f7000. y( y6 v; o0 T+ \+ x" t6 _! D5 Z% |
    arch_prctl(ARCH_SET_FS, 0x7f37720f8700) = 0
    , g: b' z, o! Q4 n; D, O+ g* n* Vmprotect(0x7f3771ee7000, 16384, PROT_READ) = 0" w7 M. a% A& z
    mprotect(0x600000, 4096, PROT_READ)     = 0
    . Y1 P) @2 H  L4 Smprotect(0x7f3772116000, 4096, PROT_READ) = 0' i7 l9 r0 M5 P3 K$ ]. g
    munmap(0x7f37720fa000, 111450)          = 0
    + J! N! b" K- xwrite(1, "BEFORE MALLOC\n", 14BEFORE MALLOC% A  L) W* U) v0 ?
    )         = 14
    ; t* X6 N/ [/ P9 O0 F9 Ibrk(NULL)                               = 0x781000# V( O- n5 ]; t0 z- n
    brk(0x7a2000)                           = 0x7a2000
    & `0 r1 B5 P# `+ a- awrite(1, "AFTER MALLOC\n", 13AFTER MALLOC; v, m  a3 v! V6 d4 U
    )          = 13! j" m* Z( i: h' d
    fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 3), ...}) = 0
    ; l( p2 a; N7 s/ Xwrite(1, "0x781010\n", 90x781010
    ! e) k  c" q4 S7 V) j& M3 f' T$ G)               = 9
    1 t6 M" a. }: Y( ?0 [fstat(0, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 3), ...}) = 0
    9 |7 \. O# m) H) m( H

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

    man brk
    6 R6 C9 A/ t* g; p...8 Y( X7 h5 E2 X6 h  K$ K4 r
           int brk(void *addr);5 u+ ]; |+ [+ h& P7 a' m
           void *sbrk(intptr_t increment);. I8 G, `7 `  D- w3 u9 A4 U
    ...' z' m  i$ ]2 U' O# ?" @. y* T) V
    DESCRIPTION
    : h0 v" W3 b% |; `+ T: x% F       brk() and sbrk() change the location of the program  break,  which  defines1 u/ {: Y3 o# f
           the end of the process's data segment (i.e., the program break is the first
    ; d5 h( O7 W" a3 [, z$ c       location after the end of the uninitialized data segment).  Increasing  the
    - ^. J! I& D! [' W       program  break has the effect of allocating memory to the process; decreas‐( B% _5 @+ J# q; O% L" D
           ing the break deallocates memory.
      k' p+ z$ Q; N8 V6 O  J
    8 I( k1 \5 G/ v' Z" [       brk() sets the end of the data segment to the value specified by addr, when+ c5 M& v- e* r
           that  value  is  reasonable,  the system has enough memory, and the process
    $ z; w  |7 S7 D0 v7 u7 ]/ P& o- s       does not exceed its maximum data size (see setrlimit(2)).
    * B" ?6 i. t0 J2 ]2 g
    ) w9 v8 X& ?- P- _# g: j       sbrk() increments the program's data space  by  increment  bytes.   Calling9 L$ n4 @+ ^6 t8 I
           sbrk()  with  an increment of 0 can be used to find the current location of
    2 j: [5 c& Q* o       the program break.& Q# D. \0 l# c& @+ F

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

    多次malloc

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

    #include <stdio.h>6 K' @. j# A% ]$ v( q
    #include <stdlib.h>
    0 T5 N+ i# |( G5 t- l#include <unistd.h>* W9 I& M; d  [* K  f
      B) _+ ?+ U. b/ K' b0 j, z$ ?
    /**1 u1 D, a. u) C- h% h2 f: F
    * main - many calls to malloc
    ! U5 F, Z9 A* {* ]! W& D) R: i */ _3 x# L# S% j1 X' N
    * Return: EXIT_FAILURE if something failed. Otherwise EXIT_SUCCESS0 m: T2 F0 ^' ~" M' n
    */# D8 R( B. |% R& H# E; l
    int main(void)
    " u5 G. T) S: @& d4 C. ]1 d4 C{
    - I" o( `. o& ]; C    void *p;3 C/ ]: w! c# k

    . ]- x" T' v* c7 N    write(1, "BEFORE MALLOC #0\n", 17);
    3 y2 t) h( K/ V9 N, s    p = malloc(1024);
    + [, S7 ?" u& r( ^  y    write(1, "AFTER MALLOC #0\n", 16);. u3 e2 W* u" L4 N. q% ~
        printf("%p\n", p);
    , k% V- a' E- T2 j& D
    ! b& ^  ~4 D$ x" P! Z0 L% s4 U. w    write(1, "BEFORE MALLOC #1\n", 17);5 ]) {% e: t- \
        p = malloc(1024);7 A+ I* f2 {/ c, L& L4 l
        write(1, "AFTER MALLOC #1\n", 16);
    2 [7 M8 Z+ C1 e# M2 T7 j# ^    printf("%p\n", p);5 _5 v' W, X' w" D  _: n
    # F0 `+ _# M4 p, I+ ?
        write(1, "BEFORE MALLOC #2\n", 17);
    0 v* b* j/ K4 ]2 t8 D+ M! _3 U    p = malloc(1024);' O. w9 [! X7 ?+ g  I+ a. o
        write(1, "AFTER MALLOC #2\n", 16);
    * `6 j& R' l# R/ D- n0 a1 I    printf("%p\n", p);
    ( R- X8 d0 i5 h( j
    " B& p3 U0 C( c    write(1, "BEFORE MALLOC #3\n", 17);
    ! v3 _' k2 ?- Y" g7 ^# c# Z- i    p = malloc(1024);
    / ^; N! k8 w! V; n7 w, v4 {    write(1, "AFTER MALLOC #3\n", 16);
    # A) c. t, s5 X8 e% n    printf("%p\n", p);+ Q6 N' E' r; R
    + d; F4 K% k+ n1 P4 k& q8 y0 h
        getchar();5 a: o% s$ j0 f2 {! F' q( j. ~
        return (EXIT_SUCCESS);
    ) R" l$ D% [. {) k}
    " w$ a, u, Q. D8 e编译运行:gcc test.c -o 5; strace ./5% X& o7 V9 _, O3 m! U& p
    摘要输出结果如下:
      ?4 t  i' e7 p& j5 E- @0 owrite(1, "BEFORE MALLOC #0\n", 17BEFORE MALLOC #0, P( v0 s6 B1 i: t
    )      = 17
    6 B9 {# W5 C% q1 L  kbrk(NULL)                               = 0x561605c7a000  o: \2 \, Q, u, ^+ e
    brk(0x561605c9b000)                     = 0x561605c9b000
    : u0 I8 L; Z1 Y+ N. S0 c; f( Kwrite(1, "AFTER MALLOC #0\n", 16AFTER MALLOC #09 H% x- E% Y5 D) W+ X
    )       = 16' i) I; E$ S# t& n# ^, X. m* X
    fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0- ~1 _! l+ }5 S. j0 j
    write(1, "0x561605c7a260\n", 150x561605c7a2603 ~* D( N' N; B$ E; W' Z; e8 w- I* T
    )        = 15. U7 s" \  m% ?1 f3 J0 _
    write(1, "BEFORE MALLOC #1\n", 17BEFORE MALLOC #1
    , u1 D) f% C3 ?& L)      = 17# v7 O9 ~  {+ h$ q8 P
    write(1, "AFTER MALLOC #1\n", 16AFTER MALLOC #1  A' B9 h! Y7 k1 h7 e
    )       = 16
    + A5 r$ X' x" W' Nwrite(1, "0x561605c7aa80\n", 150x561605c7aa80
    . z; ]1 _; ~. O. D, n, r, B)        = 15/ o+ M1 j: r$ @: }
    write(1, "BEFORE MALLOC #2\n", 17BEFORE MALLOC #2/ p1 l( o+ U' E# H: b' M+ s
    )      = 17
    $ I0 ?! o. i9 _% j# ewrite(1, "AFTER MALLOC #2\n", 16AFTER MALLOC #2) }3 I# j8 s8 c( E% s9 c0 h
    )       = 16
    ; @& P; p7 D. cwrite(1, "0x561605c7ae90\n", 150x561605c7ae90. i' r% J2 _- _+ _
    )        = 15+ m6 T$ _8 K8 j+ e  h/ A
    write(1, "BEFORE MALLOC #3\n", 17BEFORE MALLOC #3* ]6 D2 g, \5 Q# N" m
    )      = 17
    8 y* l4 r4 I& ]$ [* Wwrite(1, "AFTER MALLOC #3\n", 16AFTER MALLOC #36 w" m6 W$ ~& Z+ Y
    )       = 16
    * U  E: j; u$ O* v" x3 S! Q6 |write(1, "0x561605c7b2a0\n", 150x561605c7b2a0
    4 w7 ?( o' h6 {+ i6 W7 S; t)        = 155 D6 V/ N* K7 D' |4 B# p. r
    fstat(0, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
    ) n" h7 C$ ^8 ]8 g. O

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

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

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

    编译运行:gcc test.c -o test;./test' X6 g5 z# B/ k: \: Y( T
    输出:
    . n1 w' k, u8 A1 c& N8 q3 m0x5589436ce260
      o3 W$ h- y2 I+ l7 Qbytes at 0x5589436ce250:9 \/ C/ W  v3 o- \+ d1 k
    00 00 00 00 00 00 00 00 11 04 00 00 00 00 00 00% N- x) b1 [- d! k2 P9 t, N
    0x5589436cea80
    , @9 Z. h3 R. C3 B" lbytes at 0x5589436cea70:1 b5 D8 O8 f9 s8 |2 l$ J
    00 00 00 00 00 00 00 00 11 08 00 00 00 00 00 00, J% B$ W8 s4 d" C/ y
    0x5589436cf290) N, ~) k3 ^! i1 e' t! f
    bytes at 0x5589436cf280:
    7 C/ A# P" i1 r, ?% H5 X& n8 i2 ~00 00 00 00 00 00 00 00 11 0c 00 00 00 00 00 00
    & ]% s8 M4 h  t7 b2 \0x5589436cfea0
    5 w# l3 T  ~& H4 s5 vbytes at 0x5589436cfe90:* p; {; B5 j; z8 y6 C6 E/ z% P- f
    00 00 00 00 00 00 00 00 11 10 00 00 00 00 00 00
    1 o, W' T9 |. j, n" v+ p6 g$ N0x5589436d0eb0
    / V$ a3 R+ H6 v8 Dbytes at 0x5589436d0ea0:: [& ^! S# y3 j. k
    00 00 00 00 00 00 00 00 11 14 00 00 00 00 00 008 {& q) z! a# I1 {. H
    0x5589436d22c0
    5 C+ g+ |/ a. x9 Y' S: [+ ]bytes at 0x5589436d22b0:# i0 l, r5 i$ Z7 E) u
    00 00 00 00 00 00 00 00 11 18 00 00 00 00 00 00
    , P8 y% l6 d! F3 P4 y$ p' h0x5589436d3ad0
    0 M( u* T4 ~4 _; I0 b0 u- a( I( L# Ebytes at 0x5589436d3ac0:( x0 B1 K  g( Q
    00 00 00 00 00 00 00 00 11 1c 00 00 00 00 00 00
    4 G; ]1 l* K/ P1 y, E- d1 I0 d- M0x5589436d56e0; k5 X# m2 d, J; s5 i; o* Z# T7 I! x8 j
    bytes at 0x5589436d56d0:1 d% q2 w+ s* h
    00 00 00 00 00 00 00 00 11 20 00 00 00 00 00 00$ ~6 ]6 _4 h0 {4 @3 F! Z
    0x5589436d76f0+ n) q+ y# M4 h" }/ l
    bytes at 0x5589436d76e0:
    ( `4 U* D2 m- w9 Y2 B8 x/ Q9 U00 00 00 00 00 00 00 00 11 24 00 00 00 00 00 00
    . @" ?% e5 `% W% d0 J3 V/ Q3 o0x5589436d9b00
    / R: @; I9 U2 Q0 J. b' Tbytes at 0x5589436d9af0:
    0 P7 _: W- Q+ w2 p5 m00 00 00 00 00 00 00 00 11 28 00 00 00 00 00 00
    ) `$ c; {/ F' F' E! J5 C

    可以看出规律:这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.


    $ |1 X8 Z* ^+ G, Z  Y/ k: i可以猜测,malloc内部会把这前16个字节强转成某种数据结构,数据结构包含某些信息,最主要的是已经分配的字节数,尽管我们不了解具体结构,但是也可以通过代码操作这16个字节验证我们上面总结的规律是否正确,注意代码中不调用free释放内存.

    #include <stdio.h>3 W, ?: N6 ]0 O4 z5 e- q6 U
    #include <stdlib.h>+ H- E$ @% Q, n7 p1 P
    #include <unistd.h>4 Q/ C8 u$ A3 T9 [8 K# p  a& U9 A' q8 l

    0 X9 Q- W. e1 B1 V/**                                                                + |) f0 x$ N! v  Y4 b) y& Z
    * pmem - print mem                                                
    # e- V8 b! i# d3 _% n& A' ^ * @p: memory address to start printing from                       
    6 \( o: k, R/ P * @bytes: number of bytes to print                                
    0 P+ C6 `& m9 B+ P *                                                                 $ d$ I/ V6 S7 Q9 i/ P1 I( X
    * Return: nothing" q5 r) O+ F5 W0 @- l9 ]
    */
    ( S" h" I) N8 i* h7 n- n& hvoid pmem(void *p, unsigned int bytes)
    . j! C4 h( Q4 x! L2 d2 W{8 V6 c  C6 g5 @$ Q2 _
        unsigned char *ptr;
    1 G: n* s/ ~( s2 @$ Q    unsigned int i;; {! V- L. P0 J: D

    # P9 |( \, H& @) i2 a/ F    ptr = (unsigned char *)p;
    5 M0 H9 v0 x" w' D1 d7 F$ x    for (i = 0; i < bytes; i++)
    1 v4 E5 r* q: U) W7 ?0 i+ K7 |$ x    {5 j! n" B1 K. J, H* T% A& h2 Y0 s
            if (i != 0)
    * C  o6 q" m6 g! q* @        {4 R9 I* }4 r: j
                printf(" ");
    9 ]+ N6 }" U4 |( ]0 e5 E8 C        }
    9 v0 X. c4 n8 w        printf("%02x", *(ptr + i));
    9 t! p+ z9 B( Q  M    }
    & r. ^6 c' W5 O# e    printf("\n");
    " P- |* u, ?7 w7 x$ T# C}( F8 O3 a/ x; v- `! @

    2 K: O( y9 E, ]& g! w; Z/**5 X  G% G# O  k" D
    * main - confirm the source code
    9 \  Z- M9 }. s* W7 ~/ y" w! f *; n  B. V8 z. ^2 ?  K4 y
    * Return: EXIT_FAILURE if something failed. Otherwise EXIT_SUCCESS  R+ k3 o/ E$ K, h6 m. a
    */4 K7 e5 `/ T( n0 O' l; ~
    int main(void)& S# O& S; P( d
    {
    7 w6 m' o% {/ W: {+ M    void *p;
    9 [# \" f2 h! _' y& J2 W, d8 v    int i;
    * w+ o- F& p+ I9 D1 t* d    size_t size_of_the_chunk;
    / a0 T2 e. o/ ~/ @    size_t size_of_the_previous_chunk;
    . m: Z5 c, D+ ?* K5 M9 M. l; V    void *chunks[10];4 C3 C" z0 }7 p( v. c
    1 I9 U- q3 V, c2 F! \: Y+ E
        for (i = 0; i < 10; i++)5 j' c. l3 I' E2 j- ]
        {
    9 ]9 N  d" w% r9 d        p = malloc(1024 * (i + 1));
    6 H- H8 z- t2 P9 [: i; Y, o        chunks = (void *)((char *)p - 0x10);4 _4 L; G3 I1 [$ X9 x3 J# I
            printf("%p\n", p);4 z+ \2 ~/ G& [- G0 t* `
        }
    6 f: X+ }- |! Y( s* P    free((char *)(chunks[3]) + 0x10);9 s, l* O/ z# T$ ^* ?) ]% [2 x
        free((char *)(chunks[7]) + 0x10);
    . \3 v! S/ Z4 O3 g, j    for (i = 0; i < 10; i++)
    # u- y6 h. f4 n8 }    {
    8 C0 V5 K+ W8 ]8 k3 H: p' L9 ]        p = chunks;
    7 N+ l& o  e8 ?. Z3 r        printf("chunks[%d]: ", i);+ t" m. t2 _2 Y; h! ]' q
            pmem(p, 0x10);( p$ z7 n$ \" U( h- u
            size_of_the_chunk = *((size_t *)((char *)p + 8)) - 1;, u6 o$ N% G" L
            size_of_the_previous_chunk = *((size_t *)((char *)p));
    " B' ^% M7 n- _7 L        printf("chunks[%d]: %p, size = %li, prev = %li\n",
    ; Q: f4 U8 z2 M5 w0 G' d              i, p, size_of_the_chunk, size_of_the_previous_chunk);
    ; f2 H+ t' n- w9 ~0 P8 g3 D    }- J7 h5 [* @* h% T( S
        return (EXIT_SUCCESS);
    " ]7 ]% R6 K$ z. @8 a" W4 A}1 X# |. w* [  p" H" B/ d6 v
    编译运行输出:" r$ M- I0 w9 o* ~$ O
    root@3e8650948c0c:/ubuntu# gcc test.c -o test4 P, y7 `5 w6 j$ `* U
    root@3e8650948c0c:/ubuntu# ./test8 M; }: {, W3 Q3 Z1 N
    0x559721de4260$ \& N& \- u! M" \2 n
    0x559721de4a80
    " D/ q& C+ z+ t( T, {5 c0x559721de5290
    7 D& q; L/ q$ l0x559721de5ea0
    3 ^7 P) u( E; _/ F+ w4 [9 P5 t) U, S0x559721de6eb0
    % ~- {0 g- N7 A8 B, d3 z2 F4 A" b  c0x559721de82c01 K* a6 a( L7 G+ {
    0x559721de9ad0
    + g3 d# p; Y* s# ]0x559721deb6e0
    2 t; d% k" f" ~  i0x559721ded6f0" t! q- V, }1 k$ V: e$ W
    0x559721defb00
    ' X- ?1 f" e; w$ f# tchunks[0]: 00 00 00 00 00 00 00 00 11 04 00 00 00 00 00 00
    . B' o! t# G% Jchunks[0]: 0x559721de4250, size = 1040, prev = 0
    : _9 r; l4 H7 o. i2 }( Ochunks[1]: 00 00 00 00 00 00 00 00 11 08 00 00 00 00 00 00
    * P3 H, E- v( z) y7 a$ C' Rchunks[1]: 0x559721de4a70, size = 2064, prev = 0
    6 m! @/ x6 {& A2 C  R" X5 @( ~chunks[2]: 00 00 00 00 00 00 00 00 11 0c 00 00 00 00 00 00, t2 w( v8 ~6 V
    chunks[2]: 0x559721de5280, size = 3088, prev = 0
    9 R+ _, s, Z7 bchunks[3]: 00 00 00 00 00 00 00 00 11 10 00 00 00 00 00 00" m0 a! V( T: D- |) J6 j6 G
    chunks[3]: 0x559721de5e90, size = 4112, prev = 0
    0 J* Y  R. u( @chunks[4]: 10 10 00 00 00 00 00 00 10 14 00 00 00 00 00 00$ [" c/ b0 N. [5 M
    chunks[4]: 0x559721de6ea0, size = 5135, prev = 4112
    / h# w7 D7 G; p, fchunks[5]: 00 00 00 00 00 00 00 00 11 18 00 00 00 00 00 00
    8 g4 w. n# M2 Q" Rchunks[5]: 0x559721de82b0, size = 6160, prev = 07 _; m$ Y7 Z$ ^6 G2 l' l
    chunks[6]: 00 00 00 00 00 00 00 00 11 1c 00 00 00 00 00 002 i* V; C9 h% n" v/ h
    chunks[6]: 0x559721de9ac0, size = 7184, prev = 05 ]8 g- A! g. @- @
    chunks[7]: 00 00 00 00 00 00 00 00 11 20 00 00 00 00 00 00
    2 U8 V$ ~  V4 q- E- Hchunks[7]: 0x559721deb6d0, size = 8208, prev = 0- ]( ?0 l8 M! X$ |& l- n" l
    chunks[8]: 10 20 00 00 00 00 00 00 10 24 00 00 00 00 00 00. a5 k) z9 s- x0 M8 n+ Z7 l1 Z
    chunks[8]: 0x559721ded6e0, size = 9231, prev = 82086 G( Y2 _5 ~% X
    chunks[9]: 00 00 00 00 00 00 00 00 11 28 00 00 00 00 00 00* [8 A3 N( ^% Q0 }# l0 M% M* {
    chunks[9]: 0x559721defaf0, size = 10256, prev = 0
    ( {! `1 }3 n7 ?6 x" ?

    结果可以看出,malloc返回的地址往前的16个字节可以表示已经分配的内存大小, 如图:  O; h) _: y4 d4 j2 l, ?0 j. p/ b
    微信图片_20201119201756.png

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

    1055 /*+ x4 o, U/ q  G! c8 G2 ^
    1056       malloc_chunk details:
    ( o& b/ l: }( s' j1 d6 v1057   
    - ~( [2 j6 W% a) J: C( V- l1 B" U1058        (The following includes lightly edited explanations by Colin Plumb.)
    & U: D4 N; X8 F. D8 Z1059    ! O; d) u; A' |* f
    1060        Chunks of memory are maintained using a `boundary tag' method as6 z2 M1 |9 ?" F1 L) ^& `6 @
    1061        described in e.g., Knuth or Standish.  (See the paper by Paul
    ( b* \. i+ }1 ?1062        Wilson ftp://ftp.cs.utexas.edu/pub/garbage/allocsrv.ps for a* u( ^7 {$ G  J9 o3 G' ~) n& \
    1063        survey of such techniques.)  Sizes of free chunks are stored both
    1 l& @$ Y. H& ^$ N- @: @* R1064        in the front of each chunk and at the end.  This makes
      {) n, ?6 v) V# l1065        consolidating fragmented chunks into bigger chunks very fast.  The
    , B3 {% J$ G" I4 a/ M) `2 |1066        size fields also hold bits representing whether chunks are free or
    / r) b* D2 ~4 k5 @; M7 H1067        in use., L# H3 ^5 N' [5 d6 ^) k% s9 R
    1068    ( b9 \- D! R8 h$ ]! N* C* }
    1069        An allocated chunk looks like this:
    9 ?0 O$ Z( N" s# }' _- c+ z1070   
    . k/ R9 _( b7 i1 e$ i6 {6 @1071   
    ! k$ w0 o; z8 X3 u1072        chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+3 z7 d/ W; u  K1 d( g
    1073                |             Size of previous chunk, if unallocated (P clear)  |: U+ @8 G7 @. n( w4 ~
    1074                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+2 Z# n" t- @. J: ~8 {% ~: E
    1075                |             Size of chunk, in bytes                     |A|M|P|
    ) i$ `7 n- I- S$ u4 ^) M* }9 c: z1076          mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
    ( K# p2 r- D8 b  A) I7 [) Y1077                |             User data starts here...                          .
    ) p) H' }2 J8 Y: T1078                .                                                               .$ W6 R: S! Z+ ~, \* n4 Y1 L
    1079                .             (malloc_usable_size() bytes)                      .
    4 R. ^5 g/ f/ O: Y2 S; s& z1080                .                                                               |" {: M. x! E  F4 x' g6 Z
    1081    nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+7 w( E/ S: w  M- J" R0 ?
    1082                |             (size of chunk, but used for application data)    |2 w* y/ Y7 T* Q+ h! T: I1 X
    1083                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+6 G9 |  x% K: }! j
    1084                |             Size of next chunk, in bytes                |A|0|1|  J- a/ u1 j: S) ~
    1085                +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+2 [! ]! t) c3 v0 g
    1086    ; d' f; w, L8 Z0 }1 r
    1087        Where "chunk" is the front of the chunk for the purpose of most of- S" s* D' p' e. j% n
    1088        the malloc code, but "mem" is the pointer that is returned to the$ p& e) N0 j/ K! [
    1089        user.  "Nextchunk" is the beginning of the next contiguous chunk.4 Z) A. U2 j( E: d. W) {% M

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

    #include <stdio.h>
    3 a& ~* I( ?2 S+ a1 E4 M2 d& o#include <stdlib.h>9 `6 o0 \# Q: l) k
    #include <unistd.h>* H; d, G, d5 |* n, v
    ( ?  {1 i5 y- i0 Z- H% Z3 U
    /**                                                                                            
    3 `7 m0 y5 m3 {3 ` * pmem - print mem                                                                            * g1 @9 y( v, ~/ w- ?
    * @p: memory address to start printing from                                                   6 {# d7 i* g0 B  [! D
    * @bytes: number of bytes to print                                                            
    8 d1 O  z! Q. }8 g" I4 J *                                                                                             7 z- M" j- t# {7 Q
    * Return: nothing                                                                             
    0 j6 H% T  `$ c. r/ R */
    1 w9 W6 v1 v2 n  u1 Y+ t5 H# |void pmem(void *p, unsigned int bytes)
    2 J% ^) F) E4 {' I{2 b2 G( l% P" X; [: K7 m0 S: j
        unsigned char *ptr;$ F, }* G$ U& T3 S1 m
        unsigned int i;
    ! I; W4 M8 _6 _0 I5 y8 v9 v  r0 B6 e" U$ T$ Z
        ptr = (unsigned char *)p;
    % D7 ~3 j* ~- W! d1 ]7 _  H    for (i = 0; i < bytes; i++)6 f$ v" w8 H! T$ t7 w" [
        {
    9 ?5 \6 L. T4 y" f: Q. A        if (i != 0)
    0 \1 `0 f! c7 O6 |, \        {( Q7 e6 R- q" |7 i" k/ P
                printf(" ");  s/ g8 \2 _. d  ^( A8 G% L
            }5 U! {, g$ ?& q/ _$ W- d( g( v
            printf("%02x", *(ptr + i));
    + n! ^, f1 N$ v- v    }" u" N1 j) c9 V  y, c/ }! V# h; Z, S
        printf("\n");
    & U" q" x' v% a9 i}" F0 u% d, S6 L1 c/ S
    # B7 A" Y1 d. \8 S, M
    /**
    6 Z' X" {( W2 Q. d * main - confirm the source code
    * I  _6 E( f8 K *
    * B# J& T( h: g) g* y- i" A * Return: EXIT_FAILURE if something failed. Otherwise EXIT_SUCCESS' a! G$ [7 b2 F* S2 J
    */
    9 K2 |- u8 ~  W" @int main(void)
    " q2 g0 f, J' V- X6 f  N{# z% i! [4 B. s% Z. ~' e+ \$ p; r
        void *p;. T: S: z( B4 Z& \8 B
        int i;
    : n6 ~' i& q" p8 F3 z% q% I    size_t size_of_the_chunk;/ d  \( Y5 Q' {
        size_t size_of_the_previous_chunk;& s5 w$ o' Q, D+ d$ f
        void *chunks[10];/ F2 k* U* l9 ~

    - V8 |' I3 l, s  W; ~" c" P    for (i = 0; i < 10; i++)
    6 ]: |1 _: ^5 f5 N5 k" u    {
    6 {$ @5 O1 P3 ]& ^5 v- ~% _1 n        p = malloc(1024 * (i + 1));
    2 v0 B% U3 X, ~! c; |+ N( @        chunks = (void *)((char *)p - 0x10);% h' C! v! q+ c9 T# T
            printf("%p\n", p);+ J2 [% s# ]( c$ L5 G
        }2 {  \$ a% v, j2 T
        free((char *)(chunks[3]) + 0x10);
    0 k: J  U; R3 d    free((char *)(chunks[7]) + 0x10);/ b- c9 c" W! z9 a
        for (i = 0; i < 10; i++)
    , d! i+ |" y! n. v6 r! f  v6 s    {
    & |3 W8 g- w- ]8 K, l& I        p = chunks;
    ) Q; f( A; Z* t8 {* G        printf("chunks[%d]: ", i);
    ' p2 |. N8 E$ |& n        pmem(p, 0x10);
    1 Z+ k8 S7 \% [6 {# [        size_of_the_chunk = *((size_t *)((char *)p + 8)) - 1;
    ! y+ g8 Y. r/ z' d4 i( _        size_of_the_previous_chunk = *((size_t *)((char *)p));
    / w3 ^# q$ z; K6 t/ ?: w, m; h        printf("chunks[%d]: %p, size = %li, prev = %li\n",) ^+ h1 q8 I# A; r! W6 p
                  i, p, size_of_the_chunk, size_of_the_previous_chunk);
    ; f9 p& X! w* I/ @( i- V! b9 X; p    }
    * a& L. y" g" v+ T8 ^    return (EXIT_SUCCESS);4 q3 @$ y- x. M8 b- G' K, X
    }
    * K5 f9 T/ Z' J& I
    3 m/ _6 k, Q- m6 e3 f编译运行输出:
    ; }3 k' A* Q8 _- _* d% {" u$ troot@3e8650948c0c:/ubuntu# gcc test.c -o test& y/ \3 A* J% O
    root@3e8650948c0c:/ubuntu# ./test
    : V6 X% ?" v* t) L1 H' b$ O0x55fbebf20260
    . {( V  W3 k* Y* c, e* E0x55fbebf20a80
    . \+ p0 |& }- y1 u0x55fbebf21290
    : [7 x% t1 X, i$ L0x55fbebf21ea0
      r* s  u+ |# ~0x55fbebf22eb0  b* |& `9 n" |
    0x55fbebf242c02 e+ C- p# m$ {
    0x55fbebf25ad0
    / M8 S- s, o% s& C. I) q0x55fbebf276e0( E& i* ?& U& C' C- ?+ ?. N! o
    0x55fbebf296f0
    ' c9 [% P9 q3 Y% o3 ^1 W6 Q0x55fbebf2bb00
    " f/ @4 H. K4 C% P$ ?- t+ _* [chunks[0]: 00 00 00 00 00 00 00 00 11 04 00 00 00 00 00 00
    3 Y6 i% t8 S* Q1 N+ M7 B" z$ N! Nchunks[0]: 0x55fbebf20250, size = 1040, prev = 0$ u: k6 j% I6 @% M4 j2 s' b/ _2 }& }
    chunks[1]: 00 00 00 00 00 00 00 00 11 08 00 00 00 00 00 00
    $ M* k- }8 ^7 ]6 hchunks[1]: 0x55fbebf20a70, size = 2064, prev = 0" m; @9 d. ~' e5 a
    chunks[2]: 00 00 00 00 00 00 00 00 11 0c 00 00 00 00 00 00
    7 F- J$ V5 S( O# d6 J( Jchunks[2]: 0x55fbebf21280, size = 3088, prev = 04 T9 L" v, x! C0 W1 c
    chunks[3]: 00 00 00 00 00 00 00 00 11 10 00 00 00 00 00 00+ i* b% l( o! P! b. w
    chunks[3]: 0x55fbebf21e90, size = 4112, prev = 0* J! v' p4 T" A( S- ?9 d) b
    chunks[4]: 10 10 00 00 00 00 00 00 10 14 00 00 00 00 00 004 g, n4 e* H  D6 M7 S4 J
    chunks[4]: 0x55fbebf22ea0, size = 5135, prev = 41129 t$ G/ Z/ z* ^
    chunks[5]: 00 00 00 00 00 00 00 00 11 18 00 00 00 00 00 00; q$ \! ~6 ]1 z( d4 G
    chunks[5]: 0x55fbebf242b0, size = 6160, prev = 0
    1 E. Z# k; q1 p! Echunks[6]: 00 00 00 00 00 00 00 00 11 1c 00 00 00 00 00 00
    * Q7 U$ j1 m3 ~. @( c) @& ichunks[6]: 0x55fbebf25ac0, size = 7184, prev = 0
    0 \- F) J- F5 R, E; kchunks[7]: 00 00 00 00 00 00 00 00 11 20 00 00 00 00 00 00
    / E8 W' ?6 y* j% \, O4 ?chunks[7]: 0x55fbebf276d0, size = 8208, prev = 0
    4 X% B1 E; k0 V& }chunks[8]: 10 20 00 00 00 00 00 00 10 24 00 00 00 00 00 00
    ! `& v1 g8 b+ b% X, q  ?! Vchunks[8]: 0x55fbebf296e0, size = 9231, prev = 8208" D+ a2 }; z1 N/ D! Y0 D' M
    chunks[9]: 00 00 00 00 00 00 00 00 11 28 00 00 00 00 00 00  m4 l7 w9 ~1 `- d9 D3 y& M
    chunks[9]: 0x55fbebf2baf0, size = 10256, prev = 0  p+ J. T7 m% H$ A/ j4 b$ a" w8 i

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

    #include <stdio.h>4 f, V" d' R# x: m8 k( q
    #include <stdlib.h>. D' r4 j2 V# S" o$ ]5 W! Y
    #include <unistd.h>
    3 I3 k- q- ~% r, q! m& p2 j6 q1 \( P# G  Q  }$ C
    /**                                                                                            
    , K; k; f2 w, a. Y8 A& \: \8 L * pmem - print mem                                                                            5 ?! O& }, @7 H& d/ \. c3 O% Y* S
    * @p: memory address to start printing from                                                   ) R; a1 l9 z3 b/ J+ o
    * @bytes: number of bytes to print                                                            * Q- r6 y$ U( U' Z, ?- s* L
    *                                                                                             
      e  T8 x& `7 k/ p3 n+ {. \ * Return: nothing                                                                             
    / ?' `* V. A' G8 l5 a: \ */# q3 t$ ]# Z& {$ U* ?5 m
    void pmem(void *p, unsigned int bytes)
      z7 K: ]+ `) E, N( ^{
    2 K6 X. j2 h- r' G    unsigned char *ptr;8 A8 P8 d- s8 z! y& k
        unsigned int i;/ J% b3 |1 y* h0 @4 Q+ l- s. L% V2 ?' |3 @
    - @- Y9 F+ T) C4 q* |- t/ \  R
        ptr = (unsigned char *)p;
    0 S( V  O' Z& d0 U" I1 _    for (i = 0; i < bytes; i++)
    2 h" g8 N9 F1 R0 }" Z4 u0 i    {
    4 U2 m" F+ L; a3 w$ i: N6 ?        if (i != 0)- w+ k- O- m# y* U2 e1 l
            {5 N$ s+ F4 ?1 H$ v9 k  E7 }$ I" M
                printf(" ");
    % y1 R% q; r* {$ {! a. p- K        }( \/ V' V5 }* L" G( Z5 i! f9 w
            printf("%02x", *(ptr + i));
    ' f. F9 d4 e' h& N+ a, T: k7 M2 S    }0 Q( @, S* r( E+ f+ V/ q
        printf("\n");4 W5 p2 R, Z3 R& K
    }) p& U8 w; b; d7 a) A

    % ~* r) G% C2 v7 O' g% i/**" J& K2 G7 y  {* @5 h* y
    * main - updating with correct checks
    1 M  W" W7 ^" l) ?  _0 N *% @0 n' h5 f/ c* H; j8 Z0 g
    * Return: EXIT_FAILURE if something failed. Otherwise EXIT_SUCCESS
    $ ^% W2 {" X: o; m. N$ P */
    $ f( T1 O+ b  F' j* Mint main(void)
    ( B! B+ T( V1 W3 E- }{& e# D: k! ?2 P9 {
        void *p;1 B0 M0 v3 W- C, I- L( e
        int i;. q" w, s, L% ~- m( i: q8 K0 ^
        size_t size_of_the_chunk;0 i& C. b5 V" _: v3 q, r
        size_t size_of_the_previous_chunk;( w" {# i4 g: k& w
        void *chunks[10];; V* x% y0 x8 H  t
        char prev_used;3 B, F9 K( y9 X7 L/ A: N0 k$ p
    & k7 G0 p$ d* H
        for (i = 0; i < 10; i++), {3 `- R  x8 D6 w/ [
        {/ V7 ~+ R, B& \5 y9 \) X" A. }+ D
            p = malloc(1024 * (i + 1));
    # ?7 I3 N0 `  w7 k7 I  U        chunks = (void *)((char *)p - 0x10);5 a& q& ~2 s5 s
        }
    ( F* \6 L, J: `- Y6 y0 t& t" n    free((char *)(chunks[3]) + 0x10);, U1 H) r- t9 S; I
        free((char *)(chunks[7]) + 0x10);
    ! D+ Y" X8 |6 ]6 ^% ?/ g% m8 K    for (i = 0; i < 10; i++)
    - U" |8 \, |' p7 v8 g% C# V3 y5 k3 e! u    {
    9 L- W7 d7 o( E7 `        p = chunks;, i; \7 l9 C% u) f- ~
            printf("chunks[%d]: ", i);
    - c2 l3 W4 V$ p4 c        pmem(p, 0x10);
    ) A) B# w2 m) ~0 L. P  |        size_of_the_chunk = *((size_t *)((char *)p + 8));7 G) z# r7 o* J- [8 q
            prev_used = size_of_the_chunk & 1;! C1 M$ L+ e+ `5 ]+ {  }  [
            size_of_the_chunk -= prev_used;
    0 w" _' i$ q7 z8 X2 E# b        size_of_the_previous_chunk = *((size_t *)((char *)p));3 b6 M1 f2 z- ]. U- P% q
            printf("chunks[%d]: %p, size = %li, prev (%s) = %li\n",
    + F# A% a$ h& [" ^3 ]7 A, m              i, p, size_of_the_chunk,7 k7 T. I' y$ j3 t( h& u
                  (prev_used? "allocated": "unallocated"), size_of_the_previous_chunk);7 i! u# y; D) Z% \7 `' h
        }
    * Z& n/ J0 v/ q2 E' L2 s! O2 p    return (EXIT_SUCCESS);
    2 ?1 z- v3 H$ j- @}# i+ P- q- @  g+ U3 q" p" M9 Z( m
    编译运行输出:2 A- i* D9 p& ^: q9 N4 C5 G
    root@3e8650948c0c:/ubuntu# gcc test.c -o test
    9 m* `4 p, F; l' |) hroot@3e8650948c0c:/ubuntu# ./test. K4 ?, ]" ]% V) M- k
    chunks[0]: 00 00 00 00 00 00 00 00 11 04 00 00 00 00 00 00+ F; A; O3 i  ]* D3 Y6 a/ F
    chunks[0]: 0x56254f888250, size = 1040, prev (allocated) = 0
    : ]2 K1 o; M0 m  b& e% a: F0 hchunks[1]: 00 00 00 00 00 00 00 00 11 08 00 00 00 00 00 00" b* n) j  U# \( r" \8 X1 w: M! s7 W
    chunks[1]: 0x56254f888660, size = 2064, prev (allocated) = 0- x6 R3 z* {. Y; i  F5 l" C
    chunks[2]: 00 00 00 00 00 00 00 00 11 0c 00 00 00 00 00 00& |! s# {! u, c* S; h0 j
    chunks[2]: 0x56254f888e70, size = 3088, prev (allocated) = 08 S. s& L6 Y. r* ~. [
    chunks[3]: 00 00 00 00 00 00 00 00 11 04 00 00 00 00 00 00
    2 P! X3 i. Y9 s* A8 x9 c; {chunks[3]: 0x56254f889a80, size = 1040, prev (allocated) = 0
    9 s3 Q1 J2 Z- C9 d# Xchunks[4]: 00 0c 00 00 00 00 00 00 10 14 00 00 00 00 00 005 S, g, Q( T- W$ A) Y
    chunks[4]: 0x56254f88aa90, size = 5136, prev (unallocated) = 3072
    ) C' O; y3 [# r: }! t7 I1 Kchunks[5]: 00 00 00 00 00 00 00 00 11 18 00 00 00 00 00 003 w- `- I% @) r- `
    chunks[5]: 0x56254f88bea0, size = 6160, prev (allocated) = 0
    : }2 _& _0 |, d# mchunks[6]: 00 00 00 00 00 00 00 00 11 1c 00 00 00 00 00 00
    6 [8 J, s( F! Z( Schunks[6]: 0x56254f88d6b0, size = 7184, prev (allocated) = 0$ A; c2 j. _3 E7 y# E
    chunks[7]: 00 00 00 00 00 00 00 00 11 20 00 00 00 00 00 00; z9 X0 G8 C5 p0 R/ x2 `" o4 R5 @
    chunks[7]: 0x56254f88f2c0, size = 8208, prev (allocated) = 0
    2 E" L* q$ V/ G0 A# ochunks[8]: 10 20 00 00 00 00 00 00 10 24 00 00 00 00 00 00$ p& x! z+ }) J( l  f9 |) }
    chunks[8]: 0x56254f8912d0, size = 9232, prev (unallocated) = 8208+ Y& X  ?" ^+ }, t& b. E
    chunks[9]: 00 00 00 00 00 00 00 00 11 28 00 00 00 00 00 00
    $ E) m: ^! y5 Vchunks[9]: 0x56254f8936e0, size = 10256, prev (allocated) = 04 p/ t9 B- |9 A/ p1 p6 R
    堆空间是向上增长吗?

    通过代码验证:

    #include <stdio.h>
    , i' z; a* P* c: ~7 h#include <stdlib.h>
    0 J( B1 t( s2 J( ?' D+ |#include <unistd.h>' j( \/ R+ O. C6 W# \8 i# w
      h! i% e$ _2 @3 q! ^
    /**# J0 p; @7 w5 N  l! f" E
    * main - moving the program break
    : l8 i' ?# g8 O" e *8 g; w# y8 K. _
    * Return: EXIT_FAILURE if something failed. Otherwise EXIT_SUCCESS
    . S' |- G& U2 x' V4 D */
    ' z& x$ `4 U+ T9 Tint main(void)
    + k1 @) D! Y; V) Q. N$ @{
    % K' j$ }% I9 w8 a6 g+ Z    int i;  {3 Q4 u' b6 x

    5 h2 W8 q/ y7 K6 Z2 D9 E1 h8 ?, W7 u    write(1, "START\n", 6);
    " k+ b8 m  W5 h8 _    malloc(1);! v1 [/ d1 G! Q2 w! m: X+ u
        getchar();5 ~) S: X1 R2 S! T2 J  E- s% T( E
        write(1, "LOOP\n", 5);
    ( `; v! J% t% [9 g- v$ f* Q" J    for (i = 0; i < 0x25000 / 1024; i++)8 ]# p; d- A8 S6 x% z+ \
        {
    : ^- C/ k+ i5 T! c        malloc(1024);
    # {, O  |4 a6 k8 d6 b    }
    $ x0 Z( ^, q% @! Y0 l, y" Z/ a    write(1, "END\n", 4);' B/ a& z* H, m  ^( |
        getchar();
    & B; h: E9 u* R+ _    return (EXIT_SUCCESS);
    * A5 `# _/ t, R% q1 O2 p}
    . }) g* C# V+ d+ o8 F  X# t/ J% K# x编译运行部分摘要输出:
    # R4 Q: J8 E+ }3 P- k6 }root@3e8650948c0c:/ubuntu# gcc test.c -o test
    0 Q9 N" v7 t. E7 c& g  T" {root@3e8650948c0c:/ubuntu# strace ./test- S4 H7 n- R# ^' v
    execve("./test", ["./test"], 0x7ffe0d7cbd80 /* 10 vars */) = 00 K' c' m9 R, @3 s. a
    brk(NULL)                               = 0x555a2428f000
    8 Y! T6 l0 r! eaccess("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
    + {6 f  X1 O# o5 jaccess("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
    3 P; k, X+ l: y' q( b) R( n$ bopenat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 31 a- n5 V; C. J; Y% A; l. D# I
    fstat(3, {st_mode=S_IFREG|0644, st_size=13722, ...}) = 02 T+ `" f. I1 z' R
    mmap(NULL, 13722, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f6423455000
    - h( _& v$ u# |: K* g/ Mclose(3)                                = 0% L6 i! P2 n: `+ k
    access("/etc/ld.so.nohwcap", F_OK)      = -1 ENOENT (No such file or directory)
    6 Z% t: I: H% Ropenat(AT_FDCWD, "/lib/x86_64-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 32 M: x. B, D4 V
    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) = 832
    1 e6 w4 s. i0 }6 A1 W1 qfstat(3, {st_mode=S_IFREG|0755, st_size=2030544, ...}) = 0
    ! i  R3 ?7 B) d4 k3 ummap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f6423453000
    9 U7 ~& [6 Q+ K: }mmap(NULL, 4131552, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f6422e41000, D6 j% l5 J4 {, m/ x. w
    mprotect(0x7f6423028000, 2097152, PROT_NONE) = 0* g7 `; h: D0 E1 A. X( V2 P" E
    mmap(0x7f6423228000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1e7000) = 0x7f6423228000
    * `0 w4 P$ i1 \! E+ L7 s- J9 jmmap(0x7f642322e000, 15072, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f642322e000' U( ?4 t8 `! w( J# `5 R4 ]
    close(3)                                = 0* |" C& r3 [/ t+ Y
    arch_prctl(ARCH_SET_FS, 0x7f64234544c0) = 0( m; c1 [9 {3 Z
    mprotect(0x7f6423228000, 16384, PROT_READ) = 03 O+ v; n  t8 F4 @
    mprotect(0x555a22f5f000, 4096, PROT_READ) = 0
    / h3 E, {" q8 s3 ymprotect(0x7f6423459000, 4096, PROT_READ) = 0
    7 R6 y! u! |; h$ ~8 y: Jmunmap(0x7f6423455000, 13722)           = 0- n; M+ ]: N* ^( J) ^
    write(1, "START\n", 6START. [3 Y5 V- q; O( X. a9 O  I
    )                  = 6: I9 X0 i4 A* O/ D. K9 A: u6 x
    brk(NULL)                               = 0x555a2428f000. U6 }$ ]( s9 U6 e& A
    brk(0x555a242b0000)                     = 0x555a242b00000 x$ r$ z5 A% k6 ]+ T* V2 B) q
    fstat(0, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0  r+ b, A9 b: C* X1 G
    read(0,
    1 |. ?- C8 C  U: C/ h9 h"\n", 1024)                     = 1
    1 I+ H; a* R' Q% @9 uwrite(1, "LOOP\n", 5LOOP2 z7 U. C8 e2 t: }5 y- G6 z
    )                   = 5
    6 R9 S2 L/ B4 [1 }- y) x  rbrk(0x555a242d1000)                     = 0x555a242d1000: d! @; g. M! O( Z. M+ K" E/ H0 A  h
    brk(0x555a242f2000)                     = 0x555a242f2000
    - `8 @- n) D% d: V" C& o: Y7 Obrk(0x555a24313000)                     = 0x555a24313000" y, m$ @0 q0 b3 ]6 I4 h5 c* _
    brk(0x555a24334000)                     = 0x555a24334000
    9 I  t" T4 Y0 K' Jbrk(0x555a24355000)                     = 0x555a24355000* r, E, {) n7 Y  l) s
    brk(0x555a24376000)                     = 0x555a24376000
    0 J+ F# u5 v  ~$ c; Cbrk(0x555a24397000)                     = 0x555a243970009 d, e3 S. E9 y! S* X8 q
    brk(0x555a243b8000)                     = 0x555a243b8000
    9 L7 G1 |. }- ~brk(0x555a243d9000)                     = 0x555a243d9000
    6 @- B' ^; g7 Q4 Vbrk(0x555a243fa000)                     = 0x555a243fa000
    ( r- u2 `/ c2 x% M

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

    随机化地址空间布局

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

    [3718]: 01195000 – 00602000 = b93000$ D: }6 t6 N7 D7 d/ g
    [3834]: 024d6000 – 00602000 = 1ed4000
    - D  j! p# H( u. k1 w4 `( G[4014]: 00e70000 – 00602000 = 86e000) ^$ ?! s1 y. ~$ e7 l& W
    [4172]: 01314000 – 00602000 = d120009 q' c7 B0 p4 j: \; O" {
    [7972]: 00901000 – 00602000 = 2ff000
    : @) |: G2 Y/ \# d: @7 [/ d) E

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

    if ((current->flags & PF_RANDOMIZE) && (randomize_va_space > 1)) {
    2 v6 x; e# @" d7 C1 C                current->mm->brk = current->mm->start_brk =; Z/ |5 G7 j3 F+ Z
                            arch_randomize_brk(current->mm);
    $ R: y1 ?% r) [& R- Y4 a#ifdef compat_brk_randomized
    1 ?7 h; t  r% }                current->brk_randomized = 1;* s( C  ?. C% K& o/ H  V' p% b
    #endif6 Y7 R% b* ?5 G8 R' u+ e9 H1 a. [; ~
            }
    ' n# C% S0 t* u! E// current->mm->brk是当前进程程序中断的地址6 v. X9 w4 l5 C) c5 y: ]+ A

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

    unsigned long arch_randomize_brk(struct mm_struct *mm), f, P. ^5 [0 P
    {
    0 ?9 q( o  A# O6 H) O0 f5 M        unsigned long range_end = mm->brk + 0x02000000;
    $ N1 \( B% ~* Z" o& C3 f: i5 u        return randomize_range(mm->brk, range_end, 0) ? : mm->brk;
    - a) i7 k4 J" M! s}. h0 h$ P& L( T9 y4 `) h

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

    /*
    ; T. y' q. L! I: ?, d' L  ? * randomize_range() returns a start address such that( ^$ t' A, ~& \4 r# v- P. r) G+ `+ D
    *
    % K+ Q9 n7 Y, g3 F *    [...... <range> .....]
    : E! R" X1 Y* C5 }% W. e, R8 {2 r *  start                  end( q; ?, U: ^# `
    *
    - V4 c+ v9 R. y4 s7 `& o' s/ C' p * a <range> with size "len" starting at the return value is inside in the
      v; v+ f& k8 y2 C * area defined by [start, end], but is otherwise randomized.
    ( |- t# C& P' r+ e: Q* P */8 P& e: o& A) ]# c
    unsigned long
    : v  w6 Z( A" ?3 Zrandomize_range(unsigned long start, unsigned long end, unsigned long len)' i2 y% Y$ u9 w; s$ C2 z
    {. Q$ K% @% H. U* O
            unsigned long range = end - len - start;
    1 ?4 ]1 s, i, U( f  d" B8 Y
    ; V1 c# R. x  p# u        if (end <= start + len)
    8 H& g0 U% p' @. g! A6 p. s                return 0;* k+ ~3 f, C  w* h" \
            return PAGE_ALIGN(get_random_int() % range + start);
    : A$ x) C. f% Z, e+ p; z! ]}
    + j* `1 m) w4 _' s' l

    可以看出上面所说的这个差值其实就是0-0x02000000中的一个随机数,这种技术称为ASLR(Address Space Layout Randomisation),是一种计算机安全技术,随机安排虚拟内存中堆栈空间的位置,可以有效防止黑客攻击。通过以上分析,可以画出内存分布图如下:3 `; _1 V( z8 w
    微信图片_20201119201759.png

    malloc(0)发生了什么?

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

    #include <stdio.h>
    + g! h( F% |& H9 s+ u# `8 I; `#include <stdlib.h>  b9 X% O) x) U( i
    #include <unistd.h>
    + b! ^2 b, w6 C, j; U' y+ S9 Q  o( f/ x4 }  J& w  K9 a
    /**                                                                                            & ^5 V: a2 r5 t' _* @
    * pmem - print mem                                                                           
    " b) m6 |+ ^6 J* C, M * @p: memory address to start printing from                                                   
    8 G7 ~1 m' u4 V) Q/ o! G * @bytes: number of bytes to print                                                            3 M, ^) {4 g4 f* v, L1 W
    *                                                                                             
    $ M" F. @, X* l: _7 \  g5 q9 G8 F * Return: nothing                                                                             
    5 t3 f5 X8 H# |7 w */0 F+ N9 }: m' ?0 F& [0 H2 ?' w
    void pmem(void *p, unsigned int bytes)
    $ |: }) w. N" e5 R3 n{
    4 g& r/ m" G- s  ^& l) V4 E: @    unsigned char *ptr;0 l1 W) V  F0 ?& x' R
        unsigned int i;( ~3 F( r7 \( ^; L" Z* Q

    * a& Q9 c! v3 ~. o    ptr = (unsigned char *)p;
    $ t* p0 k6 a0 J/ D" ^    for (i = 0; i < bytes; i++)( Y. ]' E# v5 k8 Y' L: u
        {, H8 F. n; T- w% G
            if (i != 0): U$ N& s8 |8 [! Z* O5 \/ @- o
            {
    7 |5 F8 a+ j& l& b! ]  b1 Q% Z4 P            printf(" ");
    7 L3 d* |+ S+ k        }/ r( c) i# H7 T5 S
            printf("%02x", *(ptr + i));
    ) z  E6 Z+ I' Y1 H    }
    , z6 g: l* U0 p* L* A    printf("\n");$ o$ D* c! Q6 |) C4 w. v8 A
    }
    - p. |7 x+ ?7 Q$ H3 p3 y! t  C! T; H9 v. ^' D
    /**- B6 C% b: X! P3 n; g, N( A- r
    * main - moving the program break
    8 `6 g  Q" ]: C& Z7 [$ b5 T6 U; ] *' B5 _! U* _0 S/ d5 e3 ^. d
    * Return: EXIT_FAILURE if something failed. Otherwise EXIT_SUCCESS
    1 x% Y% Z, S  E& J. b4 ^3 A( o2 A */5 T7 t; s1 ]( f
    int main(void)
    " K. q8 O: I/ t6 u8 s8 d3 `9 m{' J. r  z, Q# H2 Y$ q8 k  {: I& z
        void *p;
    6 S4 N0 Z3 D0 F% f    size_t size_of_the_chunk;
    ) ?5 F; d! J; ~7 R    char prev_used;5 W6 Z) {( \) G

    . ?  M! ^- I1 i+ ^$ @1 ?    p = malloc(0);
    / V4 z2 r# o/ d4 X# ~    printf("%p\n", p);
    : G  I; F! o1 [& w5 Z    pmem((char *)p - 0x10, 0x10);
    ; B5 K1 P$ N7 f" P( V6 P7 N1 y9 T# y    size_of_the_chunk = *((size_t *)((char *)p - 8));
    2 D# [- M, A; x: D. T% ]# g2 ?: f    prev_used = size_of_the_chunk & 1;+ B$ S& ]( L, W0 B! ^
        size_of_the_chunk -= prev_used;
    9 Y4 }, Q7 }, r    printf("chunk size = %li bytes\n", size_of_the_chunk);$ u. M8 D( j7 r
        return (EXIT_SUCCESS);, L! v/ c- r. z( ~( T3 l6 Q
    }
    9 b1 [$ b1 \5 }4 |3 d* ~7 m编译运行输出如下:
    , n; o. E# h/ _, [root@3e8650948c0c:/ubuntu# gcc test.c -o test! a6 G; R4 _  i4 y% Q1 @
    root@3e8650948c0c:/ubuntu# ./test3 I, z4 x7 g9 i4 c2 ^. v1 }. F
    0x564ece64b2600 q2 \3 N* N  e4 W6 `
    00 00 00 00 00 00 00 00 21 00 00 00 00 00 00 00
    ' V8 V- E, |+ O7 M3 ]+ rchunk size = 32 bytes- I* }9 @; k4 A

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

    man malloc+ w  E) l' \2 ~7 P
    NULL may also be returned by a successful call to malloc() with a size of zero- w; Q  X6 T9 E3 M0 `
    操作环境示例代码主要在两种环境下跑过:
    - M) a! Y! C- {  E. A! Rubuntu 16.04& o  X1 g2 H! f# C- q! ?# H
    gcc (Ubuntu 7.4.0-1ubuntu1~16.04~ppa1) 7.4.0% c5 i" `( v# W& B: f- Q

    8 V& j5 v5 h% d6 f  x- `, i0 subuntu 18.04 docker
    " \% ^3 A4 J* Q5 `: q! N( }gcc (Ubuntu 7.4.0-1ubuntu1~18.04.1) 7.4.00 ~0 x# h& @2 r6 c7 P7 E
    本文是从这一系列文章翻译并结合自己理解提炼出来的,代码都自己实践过,有时间的也可以直接阅读英文原链接

    + ^- X/ r  j& z- s! H! [4 {
  • 赞 收藏 评论0 发布时间:2020-11-19 20:22

    举报

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