您當前的位置:首頁 > 攝影

徹底搞懂虛擬記憶體模型和malloc內部原理(上)

作者:由 linux 發表于 攝影時間:2021-03-29

linux伺服器開發相關影片解析:

90分鐘瞭解Linux記憶體架構,numa的優勢,slab的實現,vmalloc的原理

epoll的網路模型,從redis、memcached到nginx,一起搞定

c/c++ linux伺服器開發免費學習地址:c/c++ linux後臺伺服器高階架構師

透過/proc檔案系統探究虛擬記憶體

我們會透過/proc檔案系統找到正在執行的程序的字串所在的虛擬記憶體地址,並透過更改此記憶體地址的內容來更改字串內容,使你更深入瞭解虛擬記憶體這個概念!這之前先介紹下虛擬記憶體的定義!

虛擬記憶體

虛擬記憶體是一種實現在計算機軟硬體之間的記憶體管理技術,它將程式使用到的記憶體地址(虛擬地址)對映到計算機記憶體中的物理地址,虛擬記憶體使得應用程式從繁瑣的管理記憶體空間任務中解放出來,提高了記憶體隔離帶來的安全性,虛擬記憶體地址通常是連續的地址空間,由作業系統的記憶體管理模組控制,在觸發缺頁中斷時利用分頁技術將實際的物理記憶體分配給虛擬記憶體,而且64位機器虛擬記憶體的空間大小遠超出實際物理記憶體的大小,使得程序可以使用比物理記憶體大小更多的記憶體空間。

在深入研究虛擬記憶體前,有幾個關鍵點:

每個程序都有它自己的虛擬記憶體

虛擬記憶體的大小取決於系統的體系結構

不同操作管理有著不同的管理虛擬記憶體的方式,但大多數作業系統的虛擬記憶體結構如下圖:

徹底搞懂虛擬記憶體模型和malloc內部原理(上)

上圖並不是特別詳細的記憶體管理圖,高地址其實還有核心空間等等,但這不是這篇文章的主題。從圖中可以看到高地址儲存著命令列引數和環境變數,之後是棧空間、堆空間和可執行程式,其中棧空間向下延申,堆空間向上增長,堆空間需要使用malloc分配,是動態分配的記憶體的一部分。

首先透過一個簡單的C程式探究虛擬記憶體。

#include

#include

#include

/**

* main - 使用strdup建立一個字串的複製,strdup內部會使用malloc分配空間,

* 返回新空間的地址,這段地址空間需要外部自行使用free釋放

*

* Return: EXIT_FAILURE if malloc failed。 Otherwise EXIT_SUCCESS

*/

int main(void)

{

char *s;

s = strdup(“test_memory”);

if (s == NULL)

{

fprintf(stderr, “Can‘t allocate mem with malloc\n”);

return (EXIT_FAILURE);

}

printf(“%p\n”, (void *)s);

return (EXIT_SUCCESS);

}

編譯執行:gcc -Wall -Wextra -pedantic -Werror main。c -o test; 。/test

輸出:0x88f010

我的機器是64位機器,程序的虛擬記憶體高地址為0xffffffffffffffff, 低地址為0x0,而0x88f010遠小於0xffffffffffffffff,因此大概可以推斷出被複制的字串的地址(堆地址)是在記憶體低地址附近,具體可以透過/proc檔案系統驗證。

ls /proc目錄可以看到好多檔案,這裡主要關注/proc/[pid]/mem和/proc/[pid]/maps

mem & maps

man proc

/proc/[pid]/mem

This file can be used to access the pages of a process’s memory through open(2), read(2), and lseek(2)。

/proc/[pid]/maps

A file containing the currently mapped memory regions and their access permissions。

See mmap(2) for some further information about memory mappings。

The format of the file is:

address perms offset dev inode pathname

00400000-00452000 r-xp 00000000 08:02 173521 /usr/bin/dbus-daemon

00651000-00652000 r——p 00051000 08:02 173521 /usr/bin/dbus-daemon

00652000-00655000 rw-p 00052000 08:02 173521 /usr/bin/dbus-daemon

00e03000-00e24000 rw-p 00000000 00:00 0 [heap]

00e24000-011f7000 rw-p 00000000 00:00 0 [heap]

。。。

35b1800000-35b1820000 r-xp 00000000 08:02 135522 /usr/lib64/ld-2。15。so

35b1a1f000-35b1a20000 r——p 0001f000 08:02 135522 /usr/lib64/ld-2。15。so

35b1a20000-35b1a21000 rw-p 00020000 08:02 135522 /usr/lib64/ld-2。15。so

35b1a21000-35b1a22000 rw-p 00000000 00:00 0

35b1c00000-35b1dac000 r-xp 00000000 08:02 135870 /usr/lib64/libc-2。15。so

35b1dac000-35b1fac000 ——-p 001ac000 08:02 135870 /usr/lib64/libc-2。15。so

35b1fac000-35b1fb0000 r——p 001ac000 08:02 135870 /usr/lib64/libc-2。15。so

35b1fb0000-35b1fb2000 rw-p 001b0000 08:02 135870 /usr/lib64/libc-2。15。so

。。。

f2c6ff8c000-7f2c7078c000 rw-p 00000000 00:00 0 [stack:986]

。。。

7fffb2c0d000-7fffb2c2e000 rw-p 00000000 00:00 0 [stack]

7fffb2d48000-7fffb2d49000 r-xp 00000000 00:00 0 [vdso]

The address field is the address space in the process that the mapping occupies。

The perms field is a set of permissions:

r = read

w = write

x = execute

s = shared

p = private (copy on write)

The offset field is the offset into the file/whatever;

dev is the device (major:minor); inode is the inode on that device。 0 indicates

that no inode is associated with the memory region,

as would be the case with BSS (uninitialized data)。

The pathname field will usually be the file that is backing the mapping。

For ELF files, you can easily coordinate with the offset field

by looking at the Offset field in the ELF program headers (readelf -l)。

There are additional helpful pseudo-paths:

[stack]

The initial process‘s (also known as the main thread’s) stack。

[stack:] (since Linux 3。4)

A thread‘s stack (where the is a thread ID)。

It corresponds to the /proc/[pid]/task/[tid]/ path。

[vdso] The virtual dynamically linked shared object。

[heap] The process’s heap。

If the pathname field is blank, this is an anonymous mapping as obtained via the mmap(2) function。

There is no easy way to coordinate

this back to a process‘s source, short of running it through gdb(1), strace(1), or similar。

Under Linux 2。0 there is no field giving pathname。

透過mem檔案可以訪問和修改整個程序的記憶體頁,透過maps可以看到程序當前已對映的記憶體區域,有地址和訪問許可權偏移量等,從maps中可以看到堆空間是在低地址而棧空間是在高地址。 從maps中可以看到heap的訪問許可權是rw,即可寫,所以可以透過堆地址找到上個示例程式中字串的地址,並透過修改mem檔案對應地址的內容,就可以修改字串的內容啦,程式:

#include

#include

#include

#include

/**

* main - uses strdup to create a new string, loops forever-ever

*

* Return: EXIT_FAILURE if malloc failed。 Other never returns

*/

int main(void)

{

char *s;

unsigned long int i;

s = strdup(“test_memory”);

if (s == NULL)

{

fprintf(stderr, “Can’t allocate mem with malloc\n”);

return (EXIT_FAILURE);

}

i = 0;

while (s)

{

printf(“[%lu] %s (%p)\n”, i, s, (void *)s);

sleep(1);

i++;

}

return (EXIT_SUCCESS);

}

編譯執行:gcc -Wall -Wextra -pedantic -Werror main。c -o loop; 。/loop

輸出:

[0] test_memory (0x21dc010)

[1] test_memory (0x21dc010)

[2] test_memory (0x21dc010)

[3] test_memory (0x21dc010)

[4] test_memory (0x21dc010)

[5] test_memory (0x21dc010)

[6] test_memory (0x21dc010)

。。。

這裡可以寫一個指令碼透過/proc檔案系統找到字串所在位置並修改其內容,相應的輸出也會更改。

首先找到程序的程序號

ps aux | grep 。/loop | grep -v grep

zjucad 2542 0。0 0。0 4352 636 pts/3 S+ 12:28 0:00 。/loop

2542即為loop程式的程序號,cat /proc/2542/maps得到

00400000-00401000 r-xp 00000000 08:01 811716 /home/zjucad/wangzhiqiang/loop

00600000-00601000 r——p 00000000 08:01 811716 /home/zjucad/wangzhiqiang/loop

00601000-00602000 rw-p 00001000 08:01 811716 /home/zjucad/wangzhiqiang/loop

021dc000-021fd000 rw-p 00000000 00:00 0 [heap]

7f2adae2a000-7f2adafea000 r-xp 00000000 08:01 8661324 /lib/x86_64-linux-gnu/libc-2。23。so

7f2adafea000-7f2adb1ea000 ——-p 001c0000 08:01 8661324 /lib/x86_64-linux-gnu/libc-2。23。so

7f2adb1ea000-7f2adb1ee000 r——p 001c0000 08:01 8661324 /lib/x86_64-linux-gnu/libc-2。23。so

7f2adb1ee000-7f2adb1f0000 rw-p 001c4000 08:01 8661324 /lib/x86_64-linux-gnu/libc-2。23。so

7f2adb1f0000-7f2adb1f4000 rw-p 00000000 00:00 0

7f2adb1f4000-7f2adb21a000 r-xp 00000000 08:01 8661310 /lib/x86_64-linux-gnu/ld-2。23。so

7f2adb3fa000-7f2adb3fd000 rw-p 00000000 00:00 0

7f2adb419000-7f2adb41a000 r——p 00025000 08:01 8661310 /lib/x86_64-linux-gnu/ld-2。23。so

7f2adb41a000-7f2adb41b000 rw-p 00026000 08:01 8661310 /lib/x86_64-linux-gnu/ld-2。23。so

7f2adb41b000-7f2adb41c000 rw-p 00000000 00:00 0

7ffd51bb3000-7ffd51bd4000 rw-p 00000000 00:00 0 [stack]

7ffd51bdd000-7ffd51be0000 r——p 00000000 00:00 0 [vvar]

7ffd51be0000-7ffd51be2000 r-xp 00000000 00:00 0 [vdso]

ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]

看見堆地址範圍021dc000-021fd000,並且可讀可寫,而且021dc000<0x21dc010<021fd000,這就可以確認字串的地址在堆中,在堆中的索引是0x10(至於為什麼是0x10,後面會講到),這時可以透過mem檔案到0x21dc010地址修改內容,字串輸出的內容也會隨之更改,這裡透過python指令碼實現此功能。

#!/usr/bin/env python3

‘’‘

Locates and replaces the first occurrence of a string in the heap

of a process

Usage: 。/read_write_heap。py PID search_string replace_by_string

Where:

- PID is the pid of the target process

- search_string is the ASCII string you are looking to overwrite

- replace_by_string is the ASCII string you want to replace

search_string with

’‘’

import sys

def print_usage_and_exit():

print(‘Usage: {} pid search write’。format(sys。argv[0]))

sys。exit(1)

# check usage

if len(sys。argv) != 4:

print_usage_and_exit()

# get the pid from args

pid = int(sys。argv[1])

if pid <= 0:

print_usage_and_exit()

search_string = str(sys。argv[2])

if search_string == “”:

print_usage_and_exit()

write_string = str(sys。argv[3])

if search_string == “”:

print_usage_and_exit()

# open the maps and mem files of the process

maps_filename = “/proc/{}/maps”。format(pid)

print(“[*] maps: {}”。format(maps_filename))

mem_filename = “/proc/{}/mem”。format(pid)

print(“[*] mem: {}”。format(mem_filename))

# try opening the maps file

try:

maps_file = open(‘/proc/{}/maps’。format(pid), ‘r’)

except IOError as e:

print(“[ERROR] Can not open file {}:”。format(maps_filename))

print(“ I/O error({}): {}”。format(e。errno, e。strerror))

sys。exit(1)

for line in maps_file:

sline = line。split(‘ ’)

# check if we found the heap

if sline[-1][:-1] != “[heap]”:

continue

print(“[*] Found [heap]:”)

# parse line

addr = sline[0]

perm = sline[1]

offset = sline[2]

device = sline[3]

inode = sline[4]

pathname = sline[-1][:-1]

print(“\tpathname = {}”。format(pathname))

print(“\taddresses = {}”。format(addr))

print(“\tpermisions = {}”。format(perm))

print(“\toffset = {}”。format(offset))

print(“\tinode = {}”。format(inode))

# check if there is read and write permission

if perm[0] != ‘r’ or perm[1] != ‘w’:

print(“[*] {} does not have read/write permission”。format(pathname))

maps_file。close()

exit(0)

# get start and end of the heap in the virtual memory

addr = addr。split(“-”)

if len(addr) != 2: # never trust anyone, not even your OS :)

print(“[*] Wrong addr format”)

maps_file。close()

exit(1)

addr_start = int(addr[0], 16)

addr_end = int(addr[1], 16)

print(“\tAddr start [{:x}] | end [{:x}]”。format(addr_start, addr_end))

# open and read mem

try:

mem_file = open(mem_filename, ‘rb+’)

except IOError as e:

print(“[ERROR] Can not open file {}:”。format(mem_filename))

print(“ I/O error({}): {}”。format(e。errno, e。strerror))

maps_file。close()

exit(1)

# read heap

mem_file。seek(addr_start)

heap = mem_file。read(addr_end - addr_start)

# find string

try:

i = heap。index(bytes(search_string, “ASCII”))

except Exception:

print(“Can‘t find ’{}‘”。format(search_string))

maps_file。close()

mem_file。close()

exit(0)

print(“[*] Found ’{}‘ at {:x}”。format(search_string, i))

# write the new string

print(“[*] Writing ’{}‘ at {:x}”。format(write_string, addr_start + i))

mem_file。seek(addr_start + i)

mem_file。write(bytes(write_string, “ASCII”))

# close files

maps_file。close()

mem_file。close()

# there is only one heap in our example

break

執行這個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]:

pathname = [heap]

addresses = 021dc000-021fd000

permisions = rw-p

offset = 00000000

inode = 0

Addr start [21dc000] | end [21fd000]

[*] Found ’test_memory‘ at 10

[*] Writing ’test_hello‘ at 21dc010

同時字串輸出的內容也已更改

[633] test_memory (0x21dc010)

[634] test_memory (0x21dc010)

[635] test_memory (0x21dc010)

[636] test_memory (0x21dc010)

[637] test_memory (0x21dc010)

[638] test_memory (0x21dc010)

[639] test_memory (0x21dc010)

[640] test_helloy (0x21dc010)

[641] test_helloy (0x21dc010)

[642] test_helloy (0x21dc010)

[643] test_helloy (0x21dc010)

[644] test_helloy (0x21dc010)

[645] test_helloy (0x21dc010)

實驗成功。

透過實踐畫出虛擬記憶體空間分佈圖

再列出記憶體空間分佈圖

徹底搞懂虛擬記憶體模型和malloc內部原理(上)

基本上每個人或多或少都瞭解虛擬記憶體的空間分佈,那如何驗證它呢,下面會提到。

【文章福利】需要C/C++ Linux伺服器架構師學習資料加群812855908(資料包括C/C++,Linux,golang技術,Nginx,ZeroMQ,MySQL,Redis,fastdfs,MongoDB,ZK,流媒體,CDN,P2P,K8S,Docker,TCP/IP,協程,DPDK,ffmpeg等)

徹底搞懂虛擬記憶體模型和malloc內部原理(上)

堆疊空間

首先驗證棧空間的位置,我們都知道C中區域性變數是儲存在棧空間的,malloc分配的記憶體是儲存在堆空間,所以可以透過打印出區域性變數地址和malloc的返回記憶體地址的方式來驗證堆疊空間在整個虛擬空間中的位置。

#include

#include

#include

/**

* main - print locations of various elements

*

* Return: EXIT_FAILURE if something failed。 Otherwise EXIT_SUCCESS

*/

int main(void)

{

int a;

void *p;

printf(“Address of a: %p\n”, (void *)&a);

p = malloc(98);

if (p == NULL)

{

fprintf(stderr, “Can’t malloc\n”);

return (EXIT_FAILURE);

}

printf(“Allocated space in the heap: %p\n”, p);

return (EXIT_SUCCESS);

}

編譯執行:gcc -Wall -Wextra -pedantic -Werror main。c -o test; 。/test

輸出:

Address of a: 0x7ffedde9c7fc

Allocated space in the heap: 0x55ca5b360670

透過結果可以看出堆地址空間在棧地址空間下面,整理如圖:

徹底搞懂虛擬記憶體模型和malloc內部原理(上)

可執行程式

可執行程式也在虛擬記憶體中,可以透過列印main函式的地址,並與堆疊地址相比較,即可知道可執行程式地址相對於堆疊地址的分佈。

#include

#include

#include

/**

* main - print locations of various elements

*

* Return: EXIT_FAILURE if something failed。 Otherwise EXIT_SUCCESS

*/

int main(void)

{

int a;

void *p;

printf(“Address of a: %p\n”, (void *)&a);

p = malloc(98);

if (p == NULL)

{

fprintf(stderr, “Can‘t malloc\n”);

return (EXIT_FAILURE);

}

printf(“Allocated space in the heap: %p\n”, p);

printf(“Address of function main: %p\n”, (void *)main);

return (EXIT_SUCCESS);

}

編譯執行:gcc main。c -o test; 。/test

輸出:

Address of a: 0x7ffed846de2c

Allocated space in the heap: 0x561b9ee8c670

Address of function main: 0x561b9deb378a

由於main(0x561b9deb378a) < heap(0x561b9ee8c670) < (0x7ffed846de2c),可以畫出分佈圖如下:

徹底搞懂虛擬記憶體模型和malloc內部原理(上)

命令列引數和環境變數

程式入口main函式可以攜帶引數:

第一個引數(argc): 命令列引數的個數

第二個引數(argv): 指向命令列引數陣列的指標

第三個引數(env): 指向環境變數陣列的指標

透過程式可以看見這些元素在虛擬記憶體中的位置:

#include

#include

#include

/**

* main - print locations of various elements

*

* Return: EXIT_FAILURE if something failed。 Otherwise EXIT_SUCCESS

*/

int main(int ac, char **av, char **env)

{

int a;

void *p;

int i;

printf(“Address of a: %p\n”, (void *)&a);

p = malloc(98);

if (p == NULL)

{

fprintf(stderr, “Can’t malloc\n”);

return (EXIT_FAILURE);

}

printf(“Allocated space in the heap: %p\n”, p);

printf(“Address of function main: %p\n”, (void *)main);

printf(“First bytes of the main function:\n\t”);

for (i = 0; i < 15; i++)

{

printf(“%02x ”, ((unsigned char *)main)[i]);

}

printf(“\n”);

printf(“Address of the array of arguments: %p\n”, (void *)av);

printf(“Addresses of the arguments:\n\t”);

for (i = 0; i < ac; i++)

{

printf(“[%s]:%p ”, av[i], av[i]);

}

printf(“\n”);

printf(“Address of the array of environment variables: %p\n”, (void *)env);

printf(“Address of the first environment variable: %p\n”, (void *)(env[0]));

return (EXIT_SUCCESS);

}

編譯執行:gcc main。c -o test; 。/test nihao hello

輸出:

Address of a: 0x7ffcc154a748

Allocated space in the heap: 0x559bd1bee670

Address of function main: 0x559bd09807ca

First bytes of the main function:

55 48 89 e5 48 83 ec 40 89 7d dc 48 89 75 d0

Address of the array of arguments: 0x7ffcc154a848

Addresses of the arguments:

[。/test]:0x7ffcc154b94f [nihao]:0x7ffcc154b956 [hello]:0x7ffcc154b95c

Address of the array of environment variables: 0x7ffcc154a868

Address of the first environment variable: 0x7ffcc154b962

結果如下:

main(0x559bd09807ca) < heap(0x559bd1bee670) < stack(0x7ffcc154a748) < argv(0x7ffcc154a848) < env(0x7ffcc154a868) < arguments(0x7ffcc154b94f->0x7ffcc154b95c + 6)(6為hello+1(‘\0’)) < env first(0x7ffcc154b962)

可以看出所有的命令列引數都是相鄰的,並且緊接著就是環境變數。

argv和env陣列地址是相鄰的嗎

上例中argv有4個元素,命令列中有三個引數,還有一個NULL指向標記陣列的末尾,每個指標是8位元組,8*4=32, argv(0x7ffcc154a848) + 32(0x20) = env(0x7ffcc154a868),所以argv和env陣列指標是相鄰的。

命令列引數地址緊隨環境變數地址之後嗎

首先需要獲取環境變數陣列的大小,環境變數陣列是以NULL結束的,所以可以遍歷env陣列,檢查是否為NULL,獲取陣列大小,程式碼如下:

#include

#include

#include

/**

* main - print locations of various elements

*

* Return: EXIT_FAILURE if something failed。 Otherwise EXIT_SUCCESS

*/

int main(int ac, char **av, char **env)

{

int a;

void *p;

int i;

int size;

printf(“Address of a: %p\n”, (void *)&a);

p = malloc(98);

if (p == NULL)

{

fprintf(stderr, “Can‘t malloc\n”);

return (EXIT_FAILURE);

}

printf(“Allocated space in the heap: %p\n”, p);

printf(“Address of function main: %p\n”, (void *)main);

printf(“First bytes of the main function:\n\t”);

for (i = 0; i < 15; i++)

{

printf(“%02x ”, ((unsigned char *)main)[i]);

}

printf(“\n”);

printf(“Address of the array of arguments: %p\n”, (void *)av);

printf(“Addresses of the arguments:\n\t”);

for (i = 0; i < ac; i++)

{

printf(“[%s]:%p ”, av[i], av[i]);

}

printf(“\n”);

printf(“Address of the array of environment variables: %p\n”, (void *)env);

printf(“Address of the first environment variables:\n”);

for (i = 0; i < 3; i++)

{

printf(“\t[%p]:\”%s\“\n”, env[i], env[i]);

}

/* size of the env array */

i = 0;

while (env[i] != NULL)

{

i++;

}

i++; /* the NULL pointer */

size = i * sizeof(char *);

printf(“Size of the array env: %d elements -> %d bytes (0x%x)\n”, i, size, size);

return (EXIT_SUCCESS);

}

編譯執行:gcc main。c -o test; 。/test nihao hello

輸出:

Address of a: 0x7ffd5ebadff4

Allocated space in the heap: 0x562ba4e13670

Address of function main: 0x562ba2f1881a

First bytes of the main function:

55 48 89 e5 48 83 ec 40 89 7d dc 48 89 75 d0

Address of the array of arguments: 0x7ffd5ebae0f8

Addresses of the arguments:

[。/test]:0x7ffd5ebae94f [nihao]:0x7ffd5ebae956 [hello]:0x7ffd5ebae95c

Address of the array of environment variables: 0x7ffd5ebae118

Address of the first environment variables:

[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;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=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:”

[0x7ffd5ebaef4e]:“HOSTNAME=3e8650948c0c”

[0x7ffd5ebaef64]:“OLDPWD=/”

Size of the array env: 11 elements -> 88 bytes (0x58)

運算結果如下:

root@3e8650948c0c:/ubuntu# bc

bc 1。07。1

Copyright 1991-1994, 1997, 1998, 2000, 2004, 2006, 2008, 2012-2017 Free Software Foundation, Inc。

This is free software with ABSOLUTELY NO WARRANTY。

For details type `warranty’。

obase=16

ibase=16

58+7ffd5ebae118

(standard_in) 3: syntax error

58+7FFD5EBAE118

7FFD5EBAE170

quit

透過結果可知7FFD5EBAE170 != 0x7ffd5ebae94f,所以命令列引數地址不是緊隨環境變數地址之後。

截至目前畫出圖表如下:

徹底搞懂虛擬記憶體模型和malloc內部原理(上)

棧記憶體真的向下增長嗎

可以透過呼叫函式來確認,如果真的是向下增長,那麼呼叫函式的地址應該高於被呼叫函式地址, 程式碼如下:

#include

#include

#include

void f(void)

{

int a;

int b;

int c;

a = 98;

b = 1024;

c = a * b;

printf(“[f] a = %d, b = %d, c = a * b = %d\n”, a, b, c);

printf(“[f] Adresses of a: %p, b = %p, c = %p\n”, (void *)&a, (void *)&b, (void *)&c);

}

int main(int ac, char **av, char **env)

{

int a;

void *p;

int i;

int size;

printf(“Address of a: %p\n”, (void *)&a);

p = malloc(98);

if (p == NULL)

{

fprintf(stderr, “Can‘t malloc\n”);

return (EXIT_FAILURE);

}

printf(“Allocated space in the heap: %p\n”, p);

printf(“Address of function main: %p\n”, (void *)main);

f();

return (EXIT_SUCCESS);

}

編譯執行:gcc main。c -o test; 。/test

輸出:

Address of a: 0x7ffefc75083c

Allocated space in the heap: 0x564d46318670

Address of function main: 0x564d45b9880e

[f] a = 98, b = 1024, c = a * b = 100352

[f] Adresses of a: 0x7ffefc7507ec, b = 0x7ffefc7507f0, c = 0x7ffefc7507f4

結果可知: f{a} 0x7ffefc7507ec < main{a} 0x7ffefc75083c

可畫圖如下:

徹底搞懂虛擬記憶體模型和malloc內部原理(上)

其實也可以寫一個簡單的程式碼,透過檢視/proc檔案系統中map內容來檢視記憶體分佈,這裡就不舉例啦。

標簽: 01  35  31  00  main