10 枚の写真と 22 個のコード スニペット、仮想メモリ モデルと Malloc の内部原理を理解するのに役立つ 10,000 語の記事

10 枚の写真と 22 個のコード スニペット、仮想メモリ モデルと Malloc の内部原理を理解するのに役立つ 10,000 語の記事

[[351558]]

この記事はWeChatの公開アカウント「Programmer Cat Master」から転載したもので、著者はProgrammer Cat Masterです。この記事を転載する場合は、Programmer Catの公式アカウントまでご連絡ください。

正直に言います。もう偽りはしません。実は私は一日中一生懸命働き、家に帰って夜遅くまで公開アカウントを編集しなければならないプログラマーです。皆さんが積極的に転送して、私のKPI(夢)である1,000回の視聴を達成してくれることを願っています。ありがとう!

えーと、ちょっと話がそれてしまいました。以下はProgram Catのナンセンスです。私が Program Cat よりも人気があることを証明するために、私に少し顔を見せて、最後までスクロールして「読む」または「いいね」をクリックしてください。ありがとう!

/proc ファイルシステムを通じて仮想メモリを調べる

/proc ファイルシステムを通じて実行中のプロセスの文字列の仮想メモリ アドレスを見つけ、このメモリ アドレスの内容を変更することで文字列の内容を変更します。これにより、仮想メモリの概念をより深く理解できるようになります。その前に、まずは仮想メモリの定義を紹介しましょう!

仮想メモリ

仮想メモリは、コンピュータのソフトウェアとハ​​ードウェアの間に実装されるメモリ管理テクノロジです。プログラムが使用するメモリ アドレス (仮想アドレス) をコンピュータ メモリ内の物理アドレスにマッピングします。仮想メモリにより、アプリケーションはメモリ空間を管理する面倒な作業から解放され、メモリの分離によってもたらされるセキュリティが向上します。仮想メモリ アドレスは通常、連続したアドレス空間であり、オペレーティング システムのメモリ管理モジュールによって制御されます。ページ フォールト割り込みがトリガーされると、実際の物理メモリがページング テクノロジを使用して仮想メモリに割り当てられます。さらに、64 ビット マシンの仮想メモリのスペース サイズは実際の物理メモリのサイズをはるかに超えるため、プロセスは物理メモリ サイズよりも多くのメモリ スペースを使用できます。

仮想メモリについて詳しく説明する前に、いくつかの重要なポイントがあります。

  • 各プロセスには独自の仮想メモリがある
  • 仮想メモリのサイズはシステムアーキテクチャによって異なります。
  • オペレーティング システムによって仮想メモリの管理方法は異なりますが、ほとんどのオペレーティング システムの仮想メモリ構造は次のとおりです。

仮想メモリ.png

上の図は特に詳細なメモリ管理図ではありません。実際は高アドレスにカーネル空間などがあるのですが、これはこの記事の主題ではありません。図から、上位アドレスにはコマンドラインパラメータと環境変数が格納され、その後にスタック領域、ヒープ領域、実行可能プログラムが続くことがわかります。スタック スペースは下方向に拡張され、ヒープ スペースは上方向に拡張されます。ヒープ領域は malloc を使用して割り当てる必要があり、動的に割り当てられるメモリの一部です。

まず、簡単な C プログラムを通じて仮想メモリについて調べてみましょう。

  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. #include <文字列.h>
  4.  
  5. /**
  6. * main - strdup を使用して文字列のコピーを作成します。strdup は malloc を使用してスペースを割り当てます。
  7. * 新しいスペースのアドレスを返します。このアドレス空間は、フリーアドレスを使用して外部に解放する必要があります。
  8. *
  9. *戻り値: malloc が失敗した場合は EXIT_FAILURE を返します。それ以外の場合はEXIT_SUCCESS
  10. */
  11. intメイン(void)
  12. {
  13. 文字*s;
  14.  
  15. s = strdup( "test_memory" );
  16. s == NULL の場合
  17. {
  18. fprintf(stderr, "mallocでメモリを割り当てることができません\n" );
  19. 戻り値(EXIT_FAILURE);
  20. }
  21. printf( "%p\n" , (void *)s);
  22. 戻り値(EXIT_SUCCESS);
  23. }
  24.  
  25. コンパイルして実行します: gcc -Wall -Wextra -pedantic -Werror main.c -o test; 。/テスト
  26. 出力: 0x88f010

私のマシンは 64 ビット マシンです。プロセスの仮想メモリの上位アドレスは 0xffffffffffffffff で、下位アドレスは 0x0 です。 0x88f010 は 0xffffffffffffffffff よりはるかに小さいため、コピーされた文字列のアドレス (ヒープ アドレス) はメモリの下位アドレスに近いと大まかに推測でき、これは /proc ファイル システムを通じて確認できます。

ls /proc ディレクトリには多くのファイルが表示されます。ここでは主に/proc/[pid]/memと/proc/[pid]/mapsに焦点を当てます。

メモリとマップ

  1. 男性プロセス
  2.  
  3. /proc/[pid]/mem
  4. このファイルは、 open (2)、 read (2)、 lseek (2)を通じてプロセスのメモリページにアクセスするために使用できます。
  5.  
  6. /proc/[pid]/マップ
  7. 現在マップされているメモリ領域そのアクセス権限を含むファイル。
  8. mmap(2)参照 メモリ マッピングに関する詳細情報
  9.  
  10. ファイル形式は次のとおりです:
  11.  
  12. アドレス 権限 オフセット デバイス inode パス名
  13. 00400000-00452000 r-xp 00000000 08:02 173521 /usr/bin/dbus-daemon
  14. 00651000-00652000 r --p 00051000 08:02 173521 /usr/bin/dbus-daemon  
  15. 00652000-00655000 rw-p 00052000 08:02 173521 /usr/bin/dbus-daemon
  16. 00e03000-00e24000 rw-p 00000000 00:00 0 [ヒープ]
  17. 00e24000-011f7000 rw-p 00000000 00:00 0 [ヒープ]
  18. ...
  19. 35b1800000-35b1820000 r-xp 00000000 08:02 135522 /usr/lib64/ld-2.15.so
  20. 35b1a1f000-35b1a20000 r --p 0001f000 08:02 135522 /usr/lib64/ld-2.15.so  
  21. 35b1a20000-35b1a21000 rw-p 00020000 08:02 135522 /usr/lib64/ld-2.15.so
  22. 35b1a21000-35b1a22000 rw-p 00000000 00:00 0
  23. 35b1c00000-35b1dac000 r-xp 00000000 08:02 135870 /usr/lib64/libc-2.15.so
  24. 35b1dac000-35b1fac000 ---p 001ac000 08:02 135870 /usr/lib64/libc-2.15.so  
  25. 35b1fac000-35b1fb0000 r --p 001ac000 08:02 135870 /usr/lib64/libc-2.15.so  
  26. 35b1fb0000-35b1fb2000 rw-p 001b0000 08:02 135870 /usr/lib64/libc-2.15.so
  27. ...
  28. f2c6ff8c000-7f2c7078c000 rw-p 00000000 00:00 0 [スタック:986]
  29. ...
  30. 7fffb2c0d000-7fffb2c2e000 rw-p 00000000 00:00 0 [スタック]
  31. 7fffb2d48000-7fffb2d49000 r-xp 00000000 00:00 0 [vdso]
  32.  
  33. アドレスフィールドはアドレス空間である マッピングが占めるプロセスにおいて
  34. 権限フィールドセットです 権限:
  35.  
  36. r =読む 
  37. w = 書く
  38. x =実行 
  39. s = 共有
  40. p = プライベート(書き込み時にコピー
  41.  
  42. オフセット フィールドは、ファイルまたはその他の場所へのオフセットです
  43. dev はデバイス (メジャー:マイナー)ですinode はそのデバイス上のinodeです。 0は
  44. メモリ領域iノード関連付けられていないこと、
  45. そうであるように  BSS (初期化されていないデータ)付き
  46.  
  47. パス名フィールドは通常、マッピングをバックアップするファイルになります。
  48. ELFファイルの場合オフセットフィールド簡単に調整できます。
  49. ELF プログラム ヘッダーOffset フィールド確認します( readelf -l)。
  50.  
  51. 追加の便利な疑似パスがあります:
  52.  
  53. [スタック]
  54. 初期プロセス(メイン スレッドとも呼ばれる) のスタック。
  55.  
  56. [stack:<tid>] (Linux 3.4 以降)
  57. スレッドのスタック ( <tid>スレッドID)。
  58. これは/proc/[pid]/task/[tid]/ パス対応します。
  59.  
  60. [vdso] 仮想動的リンク共有オブジェクト。
  61.  
  62. [heap] プロセスのヒープ。
  63.  
  64. パス名フィールド空白の場合、これはmmap(2)関数を介し取得された匿名マッピングです
  65. がある 簡単に調整する方法がない
  66. これを gdb(1)、strace(1)などを通じて実行しない限り、プロセスのソース戻すことはできません
  67.  
  68. Linux 2.0では パス名を指定するフィールドがありません

mem ファイルを使用すると、プロセス全体のメモリ ページにアクセスして変更できます。マップを通じて、アドレスやアクセス許可のオフセットなど、プロセスによって現在マップされているメモリ領域を確認できます。マップから、ヒープ領域は低いアドレスにあり、スタック領域は高いアドレスにあることがわかります。マップから、ヒープのアクセス許可が rw (書き込み可能) であることがわかります。そのため、ヒープ アドレスを通じて前のサンプル プログラムの文字列のアドレスを見つけ、mem ファイルの対応するアドレスの内容を変更して、文字列の内容を変更できます。プログラム:

  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. #include <文字列.h>
  4. #include <unistd.h>
  5.  
  6. /**
  7. * main - strdupを使用し 新しい文字列を作成し、永遠にループします
  8. *
  9. *戻り値: malloc が失敗した場合は EXIT_FAILURE を返します。他は戻ってこない 
  10. */
  11. intメイン(void)
  12. {
  13. 文字*s;
  14. 符号なし long int i;
  15.  
  16. s = strdup( "test_memory" );
  17. s == NULL の場合
  18. {
  19. fprintf(stderr, "mallocでメモリを割り当てることができません\n" );
  20. 戻り値(EXIT_FAILURE);
  21. }
  22. 私 = 0;
  23. ながら
  24. {
  25. printf( "[%lu] %s (%p)\n" , i, s, (void *)s);
  26. 睡眠(1);
  27. 私は++;
  28. }
  29. 戻り値(EXIT_SUCCESS);
  30. }
  31. コンパイルして実行します: gcc -Wall -Wextra -pedantic -Werror main.c -o loop; 。/ループ
  32. 出力:
  33. [0] テストメモリ (0x21dc010)
  34. [1] テストメモリ(0x21dc010)
  35. [2] テストメモリ(0x21dc010)
  36. [3] テストメモリ(0x21dc010)
  37. [4] テストメモリ(0x21dc010)
  38. [5] テストメモリ(0x21dc010)
  39. [6] テストメモリ(0x21dc010)
  40. ...

ここで、/proc ファイルシステムを通じて文字列の場所を見つけてその内容を変更するスクリプトを記述すると、対応する出力も変更されます。

まずプロセスのプロセスIDを見つける

  1. 追伸 | grep ./ループ | grep -v grep
  2. zjucad 2542 0.0 0.0 4352 636 ポイント/3 S+ 12:28 0:00 ./loop

2542 はループ プログラムのプロセス番号であり、cat /proc/2542/maps を実行すると取得できます。

  1. 00400000-00401000 r-xp 00000000 08:01 811716 /home/zjucad/wangzhiqiang/ループ
  2. 00600000-00601000 r --p 00000000 08:01 811716 /home/zjucad/wangzhiqiang/ループ 
  3. 00601000-00602000 rw-p 00001000 08:01 811716 /home/zjucad/wangzhiqiang/ループ
  4. 021dc000-021fd000 rw-p 00000000 00:00 0 [ヒープ]
  5. 7f2adae2a000-7f2adafea000 r-xp 00000000 08:01 8661324 /lib/x86_64-linux-gnu/libc-2.23.so
  6. 7f2adafea000-7f2adb1ea000 ---p 001c0000 08:01 8661324 /lib/x86_64-linux-gnu/libc-2.23.so  
  7. 7f2adb1ea000-7f2adb1ee000 r --p 001c0000 08:01 8661324 /lib/x86_64-linux-gnu/libc-2.23.so  
  8. 7f2adb1ee000-7f2adb1f0000 rw-p 001c4000 08:01 8661324 /lib/x86_64-linux-gnu/libc-2.23.so
  9. 7f2adb1f0000-7f2adb1f4000 rw-p 00000000 00:00 0
  10. 7f2adb1f4000-7f2adb21a000 r-xp 00000000 08:01 8661310 /lib/x86_64-linux-gnu/ld-2.23.so
  11. 7f2adb3fa000-7f2adb3fd000 rw-p 00000000 00:00 0
  12. 7f2adb419000-7f2adb41a000 r --p 00025000 08:01 8661310 /lib/x86_64-linux-gnu/ld-2.23.so  
  13. 7f2adb41a000-7f2adb41b000 rw-p 00026000 08:01 8661310 /lib/x86_64-linux-gnu/ld-2.23.so
  14. 7f2adb41b000-7f2adb41c000 rw-p 00000000 00:00 0
  15. 7ffd51bb3000-7ffd51bd4000 rw-p 00000000 00:00 0 [スタック]
  16. 7ffd51bdd000-7ffd51be0000 r --p 00000000 00:00 0 [vvar]  
  17. 7ffd51be0000-7ffd51be2000 r-xp 00000000 00:00 0 [vdso]
  18. ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]

ヒープアドレスの範囲は 021dc000-021fd000 であり、読み取りおよび書き込み可能であることがわかります。また、021dc000 < 0x21dc010 < 021fd000 であるため、文字列のアドレスがヒープ内にあることが確認できます。また、ヒープ内のインデックスは 0x10 です (なぜ 0x10 なのかについては、後で説明します)。このとき、mem ファイルを通じてアドレス 0x21dc010 の内容を変更することができ、それに応じて文字列出力の内容も変化します。この関数は、ここでは Python スクリプトを通じて実装されています。

  1. #!/usr/bin/env python3
  2. '' '
  3. ヒープ内の文字列最初の出現箇所を見つけ置換します
  4. プロセス
  5.  
  6. 使用方法: ./read_write_heap.py PID search_string replace_by_string
  7. どこ
  8. -PIDは対象プロセスPIDです
  9. - search_stringは上書きしたいASCII文字列です
  10. - replace_by_stringは置換したいASCII文字列です 交換する 
  11. 検索文字列 
  12. '' '
  13.  
  14. インポートシステム
  15.  
  16. デフprint_usage_and_exit():
  17. print( '使用法: {} pid search write' .format(sys.argv[0]))
  18. sys.exit(1)
  19.  
  20. #使用方法を確認する
  21. len(sys.argv) != 4の場合:
  22. 印刷使用法と終了()
  23.  
  24. # argsからpidを取得する
  25. pid = int (sys.argv[1])
  26. pid <= 0 の場合:
  27. 印刷使用法と終了()
  28. 検索文字列 = str(sys.argv[2])
  29. 検索文字列 == ""の場合:
  30. 印刷使用法と終了()
  31. write_string = str(sys.argv[3])
  32. 検索文字列 == ""の場合:
  33. 印刷使用法と終了()
  34.  
  35. #プロセスマップメモリファイルを開く
  36. maps_filename = "/proc/{}/maps" .format(pid)
  37. print( "[*] マップ: {}" .format(maps_filename))
  38. mem_filename = "/proc/{}/mem" .format(pid)
  39. print( "[*] mem: {}" .format(mem_filename))
  40.  
  41. # マップファイルを開いてみる
  42. 試す:
  43. maps_file = open ( '/proc/{}/maps' .format(pid), 'r' )
  44. IOErrorを除き e:
  45. print( "[エラー] ファイル {} を開けません:" .format(maps_filename))
  46. print( "I/Oエラー({}): {}" .format(e.errno, e.strerror))
  47. sys.exit(1)
  48.  
  49. maps_file内の:
  50. sline = 行を分割します( ' ' )
  51. #ヒープが見つかったかどうか確認する
  52. sline[-1][:-1] != "[heap]"の場合:
  53. 続く 
  54. print( "[*] [ヒープ] が見つかりました:" )
  55.  
  56. # 行を解析する
  57. アドレス = 行[0]
  58. パーマ = sline[1]
  59. オフセット = sline[2]
  60. デバイス = sline[3]
  61. inode = sline[4]
  62. パス名 = sline[-1][:-1]
  63. print( "\tpathname = {}" .format(pathname))
  64. print( "\taddresses = {}" .format(addr))
  65. print( "\tpermissions = {}" .format(perm))
  66. print( "\toffset = {}" .format(offset))
  67. print( "\tinode = {}" .format(inode))
  68.  
  69. #あるかどうか確認する 読む 書き込み権限
  70. perm[0] != 'r'の場合 またはperm[1] != 'w' :
  71. print( "[*] {} には読み取り/書き込み権限がありません" .format(pathname))
  72. maps_file.close ( ) 関数
  73. 終了(0)
  74.  
  75. # 始めよ 終わり 仮想メモリ内のヒープ
  76. addr = addr.split( "-" )
  77. if len(addr) != 2: # 誰も信用しないでください。OSでさえ信用しないでください :)
  78. print( "[*] アドレスの形式が間違っています" )
  79. maps_file。近い()
  80. 終了(1)
  81. addr_start = int (addr[0], 16)
  82. addr_end = int (addr[1], 16)
  83. print( "\tAddr 開始 [{:x}] | 終了 [{:x}]" .format(addr_start, addr_end))
  84.  
  85. 開ける そして メモリを読む
  86. 試す:
  87. mem_file = open (mem_filename, 'rb+' )を実行します。
  88. IOErrorを除き e:
  89. print( "[エラー] ファイル {} を開けません:" .format(mem_filename))
  90. print( "I/Oエラー({}): {}" .format(e.errno, e.strerror))
  91. maps_file.close ( ) 関数
  92. 終了(1)
  93.  
  94. #ヒープ読み取り
  95. mem_file.seek(addr_start)
  96. ヒープ = mem_file。読み取り(addr_end - addr_start)
  97.  
  98. # 文字列を検索
  99. 試す:
  100. i = ヒープ。インデックス(バイト(検索文字列、 "ASCII" ))
  101. 例外を除く:
  102. print( "'{}' が見つかりません" .format(search_string))
  103. maps_file。近い()
  104. mem_file.close ( )関数
  105. 終了(0)
  106. print( "[*] {:x} に '{}' が見つかりました" .format(search_string, i))
  107.  
  108. # 新しい文字列を書き込む
  109. print( "[*] {:x} に '{}' を書き込み中" .format(write_string, addr_start + i))
  110. mem_file.seek(addr_start + i)
  111. mem_file.write(bytes(write_string, "ASCII" ))
  112.  
  113. #ファイルを閉じる
  114. maps_file。近い()
  115. mem_file.close ( )関数
  116.  
  117. がある この例ではヒープは1つだけ
  118. 壊す

このPythonスクリプトを実行する

  1. zjucad@zjucad-ONDA-H110-MINI-V3-01:~/wangzhiqiang$ sudo ./loop.py 2542 test_memory test_hello
  2. [*] マップ: /proc/2542/maps
  3. [*] メモリ: /proc/2542/mem
  4. [*] [ヒープ] が見つかりました:
  5. パス名 = [ヒープ]
  6. アドレス = 021dc000-021fd000
  7. 権限 = rw-p
  8. オフセット = 00000000
  9. iノード = 0
  10. アドレス開始 [21dc000] |終了[21fd000]
  11. [*] 'test_memory'が見つかりました  10
  12. [*] 'test_hello'を書いています  21dc010

文字列出力の内容も変更されました

  1. [633] テストメモリ (0x21dc010)
  2. [634] テストメモリ (0x21dc010)
  3. [635] テストメモリ (0x21dc010)
  4. [636] テストメモリ (0x21dc010)
  5. [637] テストメモリ (0x21dc010)
  6. [638] テストメモリ (0x21dc010)
  7. [639] テストメモリ (0x21dc010)
  8. [640] テスト_ハロイ (0x21dc010)
  9. [641] テスト_ハロイ (0x21dc010)
  10. [642] テスト_ハロイ (0x21dc010)
  11. [643] テスト_ハロイ (0x21dc010)
  12. [644] テスト_ハロイ (0x21dc010)
  13. [645] テスト_ハロイ (0x21dc010)

実験は成功した。

実践を通して仮想メモリ空​​間配分図を描く

メモリ空間配分図を再度リストします

基本的に仮想メモリの空間分布については誰もが多かれ少なかれ知っていますが、それを検証する方法については後述します。

スタックスペース

まず、スタック スペースの場所を確認します。 C のローカル変数はスタック空間に格納され、malloc によって割り当てられたメモリはヒープ空間に格納されることは誰もが知っているので、ローカル変数のアドレスと malloc の戻りメモリ アドレスを出力することで、仮想空間全体におけるスタック空間の位置を確認できます。

  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. #include <文字列.h>
  4.  
  5. /**
  6. * main -さまざまな要素印刷場所
  7. *
  8. *戻り値: 何かが失敗した場合は EXIT_FAILURE を返します。それ以外の場合はEXIT_SUCCESS
  9. */
  10. intメイン(void)
  11. {
  12. 整数a;
  13. void *p;
  14.  
  15. printf( "aのアドレス: %p\n" , (void *)&a);
  16. __p = malloc(98);
  17. (p == NULL )の場合
  18. {
  19. fprintf(stderr, "mallocできません\n" );
  20. 戻り値(EXIT_FAILURE);
  21. }
  22. printf( "ヒープ内に割り当てられたスペース: %p\n" , p);
  23. 戻り値(EXIT_SUCCESS);
  24. }
  25. コンパイルして実行します: gcc -Wall -Wextra -pedantic -Werror main.c -o test; 。/テスト
  26. 出力:
  27. アドレス: 0x7ffedde9c7fc
  28. 割り当てられたスペース ヒープ: 0x55ca5b360670

結果から、図に示すように、ヒープ アドレス空間がスタック アドレス空間の下にあることがわかります。

実行可能プログラム

実行可能プログラムも仮想メモリ内にあります。メイン関数のアドレスを出力し、それをスタック アドレスと比較することで、スタック アドレスに対する実行可能プログラム アドレスの分布を知ることができます。

  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. #include <文字列.h>
  4.  
  5. /**
  6. * main -さまざまな要素印刷場所
  7. *
  8. *戻り値: 何かが失敗した場合は EXIT_FAILURE を返します。それ以外の場合はEXIT_SUCCESS
  9. */
  10. intメイン(void)
  11. {
  12. 整数a;
  13. void *p;
  14.  
  15. printf( "aのアドレス: %p\n" , (void *)&a);
  16. __p = malloc(98);
  17. (p == NULL )の場合
  18. {
  19. fprintf(stderr, "mallocできません\n" );
  20. 戻り値(EXIT_FAILURE);
  21. }
  22. printf( "ヒープ内に割り当てられたスペース: %p\n" , p);
  23. printf( "関数mainのアドレス: %p\n" , (void *)main);
  24. 戻り値(EXIT_SUCCESS);
  25. }
  26. コンパイルして実行します: gcc main.c -o test; 。/テスト
  27. 出力:
  28. アドレス: 0x7ffed846de2c
  29. 割り当てられたスペース ヒープ: 0x561b9ee8c670
  30. 住所 関数メイン: 0x561b9deb378a

main(0x561b9deb378a) < heap(0x561b9ee8c670) < (0x7ffed846de2c) なので、分布図は次のように描くことができます。

仮想メモリスタックヒープ実行可能ファイル.png

コマンドライン引数と環境変数

プログラムエントリのメイン関数はパラメータを渡すことができます:

  • 最初のパラメータ(argc):コマンドラインパラメータの数
  • 2番目のパラメータ(argv):コマンドラインパラメータの配列へのポインタ
  • 3番目のパラメータ(env):環境変数の配列へのポインタ

プログラムは仮想メモリ内のこれらの要素の場所を確認できます。

  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. #include <文字列.h>
  4.  
  5. /**
  6. * main -さまざまな要素印刷場所
  7. *
  8. *戻り値: 何かが失敗した場合は EXIT_FAILURE を返します。それ以外の場合はEXIT_SUCCESS
  9. */
  10. int main( int ac, char **av, char **env)
  11. {
  12. 整数a;
  13. void *p;
  14. 整数i;
  15.  
  16. printf( "aのアドレス: %p\n" , (void *)&a);
  17. __p = malloc(98);
  18. (p == NULL )の場合
  19. {
  20. fprintf(stderr, "mallocできません\n" );
  21. 戻り値(EXIT_FAILURE);
  22. }
  23. printf( "ヒープ内に割り当てられたスペース: %p\n" , p);
  24. printf( "関数mainのアドレス: %p\n" , (void *)main);
  25. printf( "メイン関数の最初のバイト:\n\t" );
  26. (i = 0; i < 15; i++)の場合
  27. {
  28. printf( "%02x " , ((unsigned char *)main)[i]);
  29. }
  30. printf( "\n" );
  31. printf( "引数の配列のアドレス: %p\n" , (void *)av);
  32. printf( "引数のアドレス:\n\t" );
  33. (i = 0; i < ac; i++)の場合
  34. {
  35. printf( "[%s]:%p " , av[i], av[i]);
  36. }
  37. printf( "\n" );
  38. printf( "環境変数の配列のアドレス: %p\n" , (void *)env);
  39. printf( "最初の環境変数のアドレス: %p\n" , (void *)(env[0]));
  40. 戻り値(EXIT_SUCCESS);
  41. }
  42. コンパイルして実行します: gcc main.c -o test; ./test こんにちは
  43. 出力:
  44. アドレス: 0x7ffcc154a748
  45. 割り当てられたスペース ヒープ: 0x559bd1bee670
  46. 住所 関数メイン: 0x559bd09807ca
  47. メイン関数最初のバイト:
  48. 55 48 89 e5 48 83 ec 40 89 7d dc 48 89 75 d0
  49. 引数配列アドレス: 0x7ffcc154a848
  50. 引数アドレス:
  51. [./test]:0x7ffcc154b94f [nihao]:0x7ffcc154b956 [hello]:0x7ffcc154b95c
  52. 環境変数配列アドレス: 0x7ffcc154a868
  53. 最初環境変数アドレス: 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 つの要素があり、コマンド ラインには 3 つのパラメーターがあり、マーカー配列の末尾に NULL ポインターがあります。各ポインターは 8 バイト、8 * 4 = 32、argv(0x7ffcc154a848) + 32(0x20) = env(0x7ffcc154a868) なので、argv 配列ポインターと env 配列ポインターは隣接しています。

コマンドラインパラメータのアドレスは環境変数のアドレスに従っていますか?

まず、環境変数配列のサイズを取得する必要があります。環境変数配列は NULL で終わるので、env 配列を走査して NULL かどうかを確認し、配列のサイズを取得できます。コードは次のとおりです。

  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. #include <文字列.h>
  4.  
  5. /**
  6. * main -さまざまな要素印刷場所
  7. *
  8. *戻り値: 何かが失敗した場合は EXIT_FAILURE を返します。それ以外の場合はEXIT_SUCCESS
  9. */
  10. int main( int ac, char **av, char **env)
  11. {
  12. 整数a;
  13. void *p;
  14. 整数i;
  15. 整数 サイズ;
  16.  
  17. printf( "aのアドレス: %p\n" , (void *)&a);
  18. __p = malloc(98);
  19. (p == NULL )の場合
  20. {
  21. fprintf(stderr, "mallocできません\n" );
  22. 戻り値(EXIT_FAILURE);
  23. }
  24. printf( "ヒープ内に割り当てられたスペース: %p\n" , p);
  25. printf( "関数mainのアドレス: %p\n" , (void *)main);
  26. printf( "メイン関数の最初のバイト:\n\t" );
  27. (i = 0; i < 15; i++)の場合
  28. {
  29. printf( "%02x " , ((unsigned char *)main)[i]);
  30. }
  31. printf( "\n" );
  32. printf( "引数の配列のアドレス: %p\n" , (void *)av);
  33. printf( "引数のアドレス:\n\t" );
  34. (i = 0; i < ac; i++)の場合
  35. {
  36. printf( "[%s]:%p " , av[i], av[i]);
  37. }
  38. printf( "\n" );
  39. printf( "環境変数の配列のアドレス: %p\n" , (void *)env);
  40. printf( "最初の環境変数のアドレス:\n" );
  41. (i = 0; i < 3; i++)の場合
  42. {
  43. printf( "\t[%p]:\"%s\"\n" , env[i], env[i]);
  44. }
  45. /*サイズ  env配列*/
  46. 私 = 0;
  47. (env[i] != NULL )の間
  48. {
  49. 私は++;
  50. }
  51. 私は++; /* NULLポインタ */
  52. サイズ= i * sizeof( char * );
  53. printf( "配列envのサイズ: %d要素 -> %dバイト (0x%x)\n" , i, size , size );
  54. 戻り値(EXIT_SUCCESS);
  55. }
  56.  
  57. コンパイルして実行します: gcc main.c -o test; ./test こんにちは
  58. 出力:
  59. アドレス: 0x7ffd5ebadff4
  60. 割り当てられたスペース ヒープ: 0x562ba4e13670
  61. 住所 関数メイン: 0x562ba2f1881a
  62. メイン関数最初のバイト:
  63. 55 48 89 e5 48 83 ec 40 89 7d dc 48 89 75 d0
  64. 引数配列アドレス: 0x7ffd5ebae0f8
  65. 引数アドレス:
  66. [./test]:0x7ffd5ebae94f [nihao]:0x7ffd5ebae956 [hello]:0x7ffd5ebae95c
  67. 環境変数配列アドレス: 0x7ffd5ebae118
  68. 最初環境変数アドレス:
  69. [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:*.t lz=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=0 1;31:*.sar=01;31:*.rar=01;31:*.alz=01;31:*.ace=01;31:*.zoo=01;31:*.cpio=01;31:*.7z=01;31:*.rz=0 1;31:*.cab=01;31:*.wim=01;31:*.swm=01;31:*.dwm=01;31:*.esd=01;31:*.jpg=01;35:*.jpeg=01;35:*.mjp g=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:*.m pc=00;36:*.ogg=00;36:*.ra=00;36:*.wav=00;36:*.oga=00;36:*.opus=00;36:*.spx=00;36:*.xspf=00;36:"  
  70. [0x7ffd5ebaef4e]: "ホスト名=3e8650948c0c"  
  71. [0x7ffd5ebaef64]: "OLDPWD=/"  
  72. サイズ 配列env: 11 要素 -> 88 バイト (0x58)
  73.  
  74. 操作結果は次のとおりです。
  75. ルート@3e8650948c0c:/ubuntu#bc
  76. 1.07.1 より
  77. 著作権 1991-1994、1997、1998、2000、2004、2006、2008、2012-2017 Free Software Foundation, Inc.
  78. これ 一切の保証ない無料ソフトウェアです
  79. 詳細については、「warranty」と入力してください。
  80. オーベース=16
  81. ibase=16
  82. 58+7ffd5ebae118
  83. (standard_in) 3: 構文エラー
  84. 58+7FFD5EBAE118
  85. 7FFD5EBAE170
  86. やめる
  87. 結果は7FFD5EBAE170 != 0x7であることを示しています

結果は、7FFD5EBAE170 != 0x7ffd5ebae94f であるため、コマンド ライン パラメーターのアドレスは環境変数のアドレスに従っていないことを示しています。

これまでのチャートは次のようになります。

スタックメモリは本当に下方向に増加するのでしょうか?

これは関数を呼び出すことによって確認できます。本当に下向きに成長している場合は、呼び出し関数のアドレスが呼び出される関数のアドレスよりも高くなるはずです。コードは次のとおりです。

  1. #include <stdlib.h>
  2. #include <stdio.h>
  3. #include <文字列.h>
  4.  
  5. ボイドf(ボイド)
  6. {
  7. 整数a;
  8. 整数b;
  9. 整数c;
  10.  
  11. a = 98;
  12. 1024 です。
  13. c = a * b;
  14. printf( "[f] a = %d, b = %d, c = a * b = %d\n" , a, b, c);
  15. printf( "[f] aのアドレス: %p、b = %p、c = %p\n" 、(void *)&a、(void *)&b、(void *)&c);
  16. }
  17.  
  18. int main( int ac, char **av, char **env)
  19. {
  20. 整数a;
  21. void *p;
  22. 整数i;
  23. 整数 サイズ;
  24.  
  25. printf( "aのアドレス: %p\n" , (void *)&a);
  26. __p = malloc(98);
  27. (p == NULL )の場合
  28. {
  29. fprintf(stderr, "mallocできません\n" );
  30. 戻り値(EXIT_FAILURE);
  31. }
  32. printf( "ヒープ内に割り当てられたスペース: %p\n" , p);
  33. printf( "関数mainのアドレス: %p\n" , (void *)main);
  34. 関数f();
  35. 戻り値(EXIT_SUCCESS);
  36. }
  37. コンパイルして実行します: gcc main.c -o test; 。/テスト
  38. 出力:
  39. アドレス: 0x7ffefc75083c
  40. 割り当てられたスペース ヒープ: 0x564d46318670
  41. 住所 関数メイン: 0x564d45b9880e
  42. [f] a = 98、b = 1024、c = a * b = 100352
  43. [f] aアドレス: 0x7ffefc7507ec、b = 0x7ffefc7507f0、c = 0x7ffefc7507f4

結果は次のようになります: f{a} 0x7ffefc7507ec < main{a} 0x7ffefc75083c

次の図を描くことができます。

実際、/proc ファイル システムのマップ コンテンツを表示することで、メモリの分布を表示する簡単なコードを記述することもできます。ここでは例を挙げません。

ヒープメモリ (malloc)

メモリ割り当て

malloc は、メモリを動的に割り当てるためによく使用される関数です。 malloc によって要求されたメモリはヒープ内に割り当てられます。 malloc はシステムコールではなく、glibc 関数であることに注意してください。

男のmalloc:

  1. [...]動的メモリを割り当てる[...]
  2. void *malloc(size_tサイズ);
  3. [...]
  4. malloc()関数はサイズバイトを割り当て  割り当てられたメモリへのポインタを返します

malloc を呼び出さなければ、ヒープ領域はなくなります。

mallocを呼び出さないコードを見てみましょう

  1. #include <stdlib.h>
  2. #include <stdio.h>
  3.  
  4. /**
  5. * メイン - 何もしない
  6. *
  7. *戻り値: 何かが失敗した場合は EXIT_FAILURE を返します。それ以外の場合はEXIT_SUCCESS
  8. */
  9. intメイン(void)
  10. {
  11. char を取得する
  12. 戻り値(EXIT_SUCCESS);
  13. }
  14. コンパイルして実行します: gcc test.c -o 2; ./2
  15. ステップ 1: ps aux | grep \ \./2$
  16. 出力:
  17. zjucad 3023 0.0 0.0 4352 788 ポイント/3 S+ 13:58 0:00 ./2
  18. ステップ2: /proc/3023/maps
  19. 出力:
  20. 00400000-00401000 r-xp 00000000 08:01 811723 /home/zjucad/wangzhiqiang/2
  21. 00600000-00601000 r --p 00000000 08:01 811723 /home/zjucad/wangzhiqiang/2  
  22. 00601000-00602000 rw-p 00001000 08:01 811723 /home/zjucad/wangzhiqiang/2
  23. 007a4000-007c5000 rw-p 00000000 00:00 0 [ヒープ]
  24. 7f954ca02000-7f954cbc2000 r-xp 00000000 08:01 8661324 /lib/x86_64-linux-gnu/libc-2.23.so
  25. 7f954cbc2000-7f954cdc2000 ---p 001c0000 08:01 8661324 /lib/x86_64-linux-gnu/libc-2.23.so  
  26. 7f954cdc2000-7f954cdc6000 r --p 001c0000 08:01 8661324 /lib/x86_64-linux-gnu/libc-2.23.so  
  27. 7f954cdc6000-7f954cdc8000 rw-p 001c4000 08:01 8661324 /lib/x86_64-linux-gnu/libc-2.23.so
  28. 7f954cdc8000-7f954cdcc000 rw-p 00000000 00:00 0
  29. 7f954cdcc000-7f954cdf2000 r-xp 00000000 08:01 8661310 /lib/x86_64-linux-gnu/ld-2.23.so
  30. 7f954cfd2000-7f954cfd5000 rw-p 00000000 00:00 0
  31. 7f954cff1000-7f954cff2000 r --p 00025000 08:01 8661310 /lib/x86_64-linux-gnu/ld-2.23.so  
  32. 7f954cff2000-7f954cff3000 rw-p 00026000 08:01 8661310 /lib/x86_64-linux-gnu/ld-2.23.so
  33. 7f954cff3000-7f954cff4000 rw-p 00000000 00:00 0
  34. 7ffed68a1000-7ffed68c2000 rw-p 00000000 00:00 0 [スタック]
  35. 7ffed690e000-7ffed6911000 r --p 00000000 00:00 0 [vvar]  
  36. 7ffed6911000-7ffed6913000 r-xp 00000000 00:00 0 [vdso]
  37. ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]

ご覧のとおり、mallocが呼び出されない場合、マップには[ヒープ]がありません。

malloc を使ってプログラムを実行してみましょう。

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3.  
  4. /**
  5. * main - malloc が返したアドレスを出力します
  6. *
  7. *戻り値: 何かが失敗した場合は EXIT_FAILURE を返します。それ以外の場合はEXIT_SUCCESS
  8. */
  9. intメイン(void)
  10. {
  11. void *p;
  12.  
  13. 1. 呼び出し側で malloc() を呼び出す。
  14. printf( "%p\n" , p);
  15. char を取得する
  16. 戻り値(EXIT_SUCCESS);
  17. }
  18. コンパイルして実行: gcc test.c -o 3; ./3
  19. 出力: 0xcc7010
  20. 検証手順と出力:
  21. zjucad@zjucad-ONDA-H110-MINI-V3-01:~/wangzhiqiang$ ps aux | grep \ \./3$
  22. zjucad 3113 0.0 0.0 4352 644 ポイント/3 S+ 14:06 0:00 ./3
  23. zjucad@zjucad-ONDA-H110-MINI-V3-01:~/wangzhiqiang$ cat /proc/3113/maps
  24. 00400000-00401000 r-xp 00000000 08:01 811726 /home/zjucad/wangzhiqiang/3
  25. 00600000-00601000 r --p 00000000 08:01 811726 /home/zjucad/wangzhiqiang/3  
  26. 00601000-00602000 rw-p 00001000 08:01 811726 /home/zjucad/wangzhiqiang/3
  27. 00cc7000-00ce8000 rw-p 00000000 00:00 0 [ヒープ]
  28. 7fc7e9128000-7fc7e92e8000 r-xp 00000000 08:01 8661324 /lib/x86_64-linux-gnu/libc-2.23.so
  29. 7fc7e92e8000-7fc7e94e8000 ---p 001c0000 08:01 8661324 /lib/x86_64-linux-gnu/libc-2.23.so  
  30. 7fc7e94e8000-7fc7e94ec000 r --p 001c0000 08:01 8661324 /lib/x86_64-linux-gnu/libc-2.23.so  
  31. 7fc7e94ec000-7fc7e94ee000 rw-p 001c4000 08:01 8661324 /lib/x86_64-linux-gnu/libc-2.23.so
  32. 7fc7e94ee000-7fc7e94f2000 rw-p 00000000 00:00 0
  33. 7fc7e94f2000-7fc7e9518000 r-xp 00000000 08:01 8661310 /lib/x86_64-linux-gnu/ld-2.23.so
  34. 7fc7e96f8000-7fc7e96fb000 rw-p 00000000 00:00 0
  35. 7fc7e9717000-7fc7e9718000 r --p 00025000 08:01 8661310 /lib/x86_64-linux-gnu/ld-2.23.so  
  36. 7fc7e9718000-7fc7e9719000 rw-p 00026000 08:01 8661310 /lib/x86_64-linux-gnu/ld-2.23.so
  37. 7fc7e9719000-7fc7e971a000 rw-p 00000000 00:00 0
  38. 7ffc91c18000-7ffc91c39000 rw-p 00000000 00:00 0 [スタック]
  39. 7ffc91d5f000-7ffc91d62000 r --p 00000000 00:00 0 [vvar]  
  40. 7ffc91d62000-7ffc91d64000 r-xp 00000000 00:00 0 [vdso]
  41. ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]

プログラムに malloc が含まれている場合、マップ内に [heap] セグメントがあり、malloc によって返されるアドレスはヒープ内のアドレス セグメント内にありますが、返されるアドレスはヒープ内の先頭ではなくなり、0x10 バイトの差が生じます。なぜ?以下を参照してください:

strace、brk、sbrk

malloc はシステムコールではなく、通常の関数です。ヒープメモリを操作するには、特定のシステムコールを呼び出す必要があります。プロセスのシステムコールとシグナルは、strace ツールを使用して追跡できます。システム コールが malloc によって生成されたことを確認するために、問題の特定を容易にするために、 malloc の前後に write システム コールが追加されます。

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4.  
  5. /**
  6. * main -どのシステムコール malloc使用されているか調べます
  7. *
  8. *戻り値: 何かが失敗した場合は EXIT_FAILURE を返します。それ以外の場合はEXIT_SUCCESS
  9. */
  10. intメイン(void)
  11. {
  12. void *p;
  13.  
  14. write(1, "BEFORE MALLOC\n" , 14);
  15. 1. 呼び出し側で malloc() を呼び出す。
  16. write(1, "AFTER MALLOC\n" , 13);
  17. printf( "%p\n" , p);
  18. char を取得する
  19. 戻り値(EXIT_SUCCESS);
  20. }
  21. コンパイルして実行: gcc test.c -o 4
  22. zjucad@zjucad-ONDA-H110-MINI-V3-01:~/wangzhiqiang$ strace ./4
  23. execve( "./4" , [ "./4" ], [/* 34 個の変数 */]) = 0
  24. brk( NULL ) = 0x781000
  25. access( "/etc/ld.so.nohwcap" , F_OK) = -1 ENOENT (そのようなファイルまたはディレクトリはありません)
  26. access( "/etc/ld.so.preload" , R_OK) = -1 ENOENT (そのようなファイルまたはディレクトリはありません)
  27. オープン( "/etc/ld.so.cache" 、 O_RDONLY|O_CLOEXEC) = 3
  28. fstat(3, {st_mode=S_IFREG|0644, st_size=111450, ...}) = 0
  29. mmap( NULL , 111450, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f37720fa000
  30. 閉じる(3)= 0
  31. access( "/etc/ld.so.nohwcap" , F_OK) = -1 ENOENT (そのようなファイルまたはディレクトリはありません)
  32. オープン( "/lib/x86_64-linux-gnu/libc.so.6" 、 O_RDONLY|O_CLOEXEC) = 3
  33. 読み取り(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
  34. fstat(3, {st_mode=S_IFREG|0755, st_size=1868984, ...}) = 0
  35. mmap( NULL , 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f37720f9000
  36. mmap( NULL 、 3971488 、 PROT_READ|PROT_EXEC 、 MAP_PRIVATE|MAP_DENYWRITE 、 3 、 0) = 0x7f3771b27000
  37. mprotect(0x7f3771ce7000, 2097152, PROT_NONE) = 0
  38. mmap(0x7f3771ee7000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c0000) = 0x7f3771ee7000
  39. mmap(0x7f3771eed000, 14752, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f3771eed000
  40. 閉じる(3)= 0
  41. mmap( NULL , 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f37720f8000
  42. mmap( NULL , 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f37720f7000
  43. arch_prctl(ARCH_SET_FS, 0x7f37720f8700) = 0
  44. mprotect(0x7f3771ee7000, 16384, PROT_READ) = 0
  45. mprotect(0x600000, 4096, PROT_READ) = 0
  46. mprotect(0x7f3772116000, 4096, PROT_READ) = 0
  47. マンマップ(0x7f37720fa000, 111450) = 0
  48. write(1, "BEFORE MALLOC\n" , 14BEFORE MALLOC
  49. ) = 14
  50. brk( NULL ) = 0x781000
  51. brk(0x7a2000) = 0x7a2000
  52. write(1, "MALLOC後\n" , 13MALLOC後
  53. ) = 13
  54. fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 3), ...}) = 0
  55. 書き込み(1, "0x781010\n" , 90x781010
  56. ) = 9
  57. fstat(0, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 3), ...}) = 0

最後の数行の出力から、 malloc は主に brk システム コールを呼び出してヒープ メモリを操作していることがわかります。

  1. 男brk
  2. ...
  3. int brk(void *addr);
  4. void *sbrk(intptr_t 増分);
  5. ...
  6. 説明
  7. brk()sbrk()はプログラムブレーク位置を変更します
  8. 終わり プロセスのデータセグメント(つまり、プログラムブレーク最初 
  9. 終了後の場所 初期化されていないデータ セグメント)。増加
  10. プログラムブレークはプロセスメモリを割り当てる効果があります。減少する‐
  11. ブレークを実行するとメモリが確保されます。
  12.  
  13. brk()は終了を設定します データセグメント値をaddr指定れた値 
  14. その値合理的で、システムには十分なメモリがありプロセス
  15. 最大データサイズを超えませ(Setrlimit(2)を参照)。
  16.  
  17. SBRK()は、プログラムのデータ空間を増加させます  増分バイト。通話
  18. 0増分のSBRK()を使用して現在の位置を見つけることできます 
  19. プログラムが壊れます。

プログラム割り込みは、仮想メモリのプログラムデータセグメントの終了後の最初の場所のアドレスです。 MallocはBRKまたはSBRKを呼び出し、プログラム割り込みの値を増やして、メモリを動的に割り当てる新しいスペースを作成します。 BRKへの最初の呼び出しは、現在のプログラム割り込みのアドレスを返し、BRKへの2回目の呼び出しもプログラム割り込みのアドレスを返します。 2番目のBRKの返品アドレスは、プログラム割り込みアドレスを増やすことにより、最初のBRK.BRKの返品アドレスよりも大きいことがわかります。ヒープアドレス範囲が表示されることがわかります。また、CAT/Proc/[PID]/マップを介して検証することもできます。実際の検証結果はここには投稿されません。

複数のマロック

mallocが複数回である場合はどうなりますか?コードは次のとおりです。

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4.  
  5. /**
  6. *メイン -マロックへの多くの呼び出し
  7. *
  8. *返品:何かが失敗した場合はexit_failure。それ以外の場合はexit_success
  9. */
  10. int main(void)
  11. {
  12. void *p;
  13.  
  14. 書き込み(1、 "malloc#0 \ n" 、17);
  15. p = malloc(1024);
  16. write(1、 "malloc#0 \ n" 、16);
  17. printf( "%p \ n" 、p);
  18.  
  19. 書き込み(1、 "malloc#1 \ nの前" 、17);
  20. p = malloc(1024);
  21. 書き込み(1、 "後のmalloc#1 \ n" 、16);
  22. printf( "%p \ n" 、p);
  23.  
  24. 書き込み(1、 "malloc#2 \ nの前" 、17);
  25. p = malloc(1024);
  26. 書き込み(1、 "malloc#2 \ nの後" 、16);
  27. printf( "%p \ n" 、p);
  28.  
  29. 書き込み(1、 "malloc#3 \ nの前" 、17);
  30. p = malloc(1024);
  31. 書き込み(1、 "malloc#3 \ nの後" 、16);
  32. printf( "%p \ n" 、p);
  33.  
  34. getchar();
  35. return (exit_success);
  36. }
  37. コンパイルと実行:gcc test.c -o 5; Strace ./5
  38. 概要出力の結果は次のとおりです。
  39. 書き込み(1、 "malloc#0 \ nの前" 、17before malloc#0
  40. )= 17
  41. BRK( null )= 0x561605c7a000
  42. brk(0x561605c9b000)= 0x561605c9b000
  43. 書き込み(1、 "後のmalloc#0 \ n" 、16after malloc#0
  44. )= 16
  45. fstat(1、{st_mode = s_ifchr | 0620、st_rdev = makedev(136、0)、...})= 0
  46. 書き込み(1、 "0x561605c7a260 \ n" 、150x561605c7a260
  47. )= 15
  48. 書き込み(1、 "malloc#1 \ nの前" 、17before malloc#1
  49. )= 17
  50. 書き込み(1、 "後のmalloc#1 \ n" 、16after malloc#1
  51. )= 16
  52. 書き込み(1、 "0x561605c7aa80 \ n" 、150x561605c7aa80
  53. )= 15
  54. 書き込み(1、 "malloc#2 \ nの前" 、17before malloc#2
  55. )= 17
  56. 書き込み(1、 "後のmalloc#2 \ n" 、16after malloc#2
  57. )= 16
  58. 書き込み(1、 "0x561605c7ae90 \ n" 、150x561605c7ae90
  59. )= 15
  60. 書き込み(1、 "malloc#3 \ nの前" 、17before malloc#3
  61. )= 17
  62. 書き込み(1、 "後のmalloc#3 \ n" 、16after malloc#3
  63. )= 16
  64. 書き込み(1、 "0x561605c7b2a0 \ n" 、150x561605c7b2a0
  65. )= 15
  66. fstat(0、{st_mode = s_ifchr | 0620、st_rdev = makedev(136、0)、...})= 0

Mallocが呼び出されるたびに、BRKシステムコールがトリガーされるわけではないことがわかります。 Mallocが初めて呼び出されると、プログラム割り込みアドレスがBRKシステムコールを介して変更され、大きなメモリスペースが割り当てられます。マロックは後で呼び出されます。以前に割り当てられたメモリスペースが最初に使用されます。 BRKシステムコールは、内部メモリスペースが再び外側に割り当てるのに十分ではないまで、再びトリガーされません。

0x10欠落している16バイトは何ですか

上記の分析は、プログラムのMallocへの最初の呼び出しによって返されたアドレスが、ヒープセグメントの最初のアドレスではなく、0x10バイトであることを示しています。では、これらの16バイトは正確には何ですか?最初の16バイトのコンテンツは、プログラムを通じて印刷できます。

  1. コンパイルと実行:gcc test.c -o test; ./ test
  2. 出力:
  3. 0x5589436ce260
  4. 0x5589436ce250バイト
  5. 00 00 00 00 00 00 00 00 00 11 04 00 00 00 00 00
  6. 0x5589436CEA80
  7. 0x5589436CEA70バイト
  8. 00 00 00 00 00 00 00 00 00 00 11 08 00 00 00 00 00
  9. 0x5589436CF290
  10. 0x5589436CF280バイト
  11. 00 00 00 00 00 00 00 00 00 11 0C 00 00 00 00 00 00
  12. 0x5589436cfea0
  13. 0x5589436cfe90バイト
  14. 00 00 00 00 00 00 00 00 00 11 10 00 00 00 00 00 00
  15. 0x5589436D0EB0
  16. 0x5589436d0ea0バイト
  17. 00 00 00 00 00 00 00 00 00 11 14 00 00 00 00 00 00
  18. 0x5589436D22C0
  19. 0x5589436d22b0バイト:
  20. 00 00 00 00 00 00 00 00 00 00 11 18 00 00 00 00 00
  21. 0x5589436d3ad0
  22. 0x5589436d3ac0バイト
  23. 00 00 00 00 00 00 00 00 00 11 1C 00 00 00 00 00
  24. 0x5589436D56E0
  25. 0x5589436d56d0バイト:
  26. 00 00 00 00 00 00 00 00 00 11 20 00 00 00 00 00 00
  27. 0x5589436D76F0
  28. 0x5589436d76e0バイト:
  29. 00 00 00 00 00 00 00 00 00 11 24 00 00 00 00 00 00
  30. 0x5589436d9b00
  31. 0x5589436D9AF0バイト
  32. 00 00 00 00 00 00 00 00 00 00 11 28 00 00 00 00 00 00

ルールを見ることができます。これらの16バイトは、いくつかの情報を含むMallocによって作成されたアドレスのヘッダーに相当します。現在、すでに割り当てられているアドレススペースのサイズが含まれていることがわかります。 Mallocが0x400(1024)バイトに初めて適用されたとき、11 04 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 04 11 = 0x400(1024) + 0x10(ヘッダー16のサイズ) + 1(意味が後で説明されます)。 Mallocが呼び出されるたびに、最初の8バイトはMallocバイト +16 +1の意味を表すことがわかります。

最初の16バイトがMalloc内の特定のデータ構造に強制されると推測できます。データ構造には特定の情報が含まれており、最も重要なことは、割り当てられたバイトの数です。特定の構造は理解していませんが、コードを使用してこれらの16バイトを操作して、上記のルールが正しいかどうかを確認することもできます。コードは自由に呼び出されてメモリをリリースしないことに注意してください。

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4.  
  5. /**
  6. * PMEM -MEMを印刷します
  7. * @P:印刷を開始するメモリアドレス                        
  8. * @bytes:印刷するバイト
  9. *
  10. *返品:何もありません
  11. */
  12. void pmem(void *p、unsigned int bytes)
  13. {
  14. 符号なしのchar *ptr;
  15. 署名されていないint i;
  16.  
  17. ptr =(unsigned char *)p;
  18. for (i = 0; i <bytes; i ++)
  19. {
  20. if(i!= 0)
  21. {
  22. printf( "" );
  23. }
  24. printf( "%02x" 、 *(ptr + i));
  25. }
  26. printf( "\ n" );
  27. }
  28.  
  29. /**
  30. *メイン - ソースコードを確認します
  31. *
  32. *返品:何かが失敗した場合はexit_failure。それ以外の場合はexit_success
  33. */
  34. int main(void)
  35. {
  36. void *p;
  37. int i;
  38. size_t size_of_the_chunk;
  39. size_t size_of_the_previous_chunk;
  40. void *チャンク[10];
  41.  
  42. for (i = 0; i <10; i ++)
  43. {
  44. p = malloc(1024 *(i + 1));
  45. チャンク[i] =(void *)(( char *)p -0x10);
  46. printf( "%p \ n" 、p);
  47. }
  48. free (( char *)(chunks [3]) + 0x10);
  49. free (( char *)(chunks [7]) + 0x10);
  50. for (i = 0; i <10; i ++)
  51. {
  52. p =チャンク[i];
  53. printf( "chunks [%d]:" 、i);
  54. PMEM(P、0x10);
  55. size_of_the_chunk = *((size_t *)(( char *)p + 8))-1;
  56. size_of_the_previous_chunk = *((size_t *)(( char *)p));
  57. printf( "チャンク[%d]:%p、size =%li、prev =%li \ n"
  58. i、p、size_of_the_chunk、size_of_the_previous_chunk);
  59. }
  60. return (exit_success);
  61. }
  62. コンパイルして出力を実行します:
  63. root@3e8650948c0c:/ubuntu#gcc test.c -oテスト
  64. root@3e8650948c0c:/ubuntu#./test
  65. 0x559721DE4260
  66. 0x559721DE4A80
  67. 0x559721DE5290
  68. 0x559721DE5EA0
  69. 0x559721DE6EB0
  70. 0x559721DE82C0
  71. 0x559721DE9AD0
  72. 0x559721DEB6E0
  73. 0x559721ded6f0
  74. 0x559721DEFB00
  75. チャンク[0]:00 00 00 00 00 00 00 00 11 04 00 00 00 00 00 00
  76. チャンク[0]:0x559721DE4250、 size = 1040、prev = 0
  77. チャンク[1]:00 00 00 00 00 00 00 00 11 08 00 00 00 00 00 00
  78. チャンク[1]:0x559721de4a70、 size = 2064、prev = 0
  79. チャンク[2]:00 00 00 00 00 00 00 00 11 0C 00 00 00 00 00 00
  80. チャンク[2]:0x559721DE5280、 size = 3088、prev = 0
  81. チャンク[3]:00 00 00 00 00 00 00 00 11 10 00 00 00 00 00 00 00
  82. チャンク[3]:0x559721DE5E90、 size = 4112、prev = 0
  83. チャンク[4]:10 10 00 00 00 00 00 00 00 10 14 00 00 00 00 00 00
  84. チャンク[4]:0x559721DE6EA0、 size = 5135、prev = 4112
  85. チャンク[5]:00 00 00 00 00 00 00 00 11 18 00 00 00 00 00 00
  86. チャンク[5]:0x559721de82b0、 size = 6160、prev = 0
  87. チャンク[6]:00 00 00 00 00 00 00 00 11 1C 00 00 00 00 00 00
  88. チャンク[6]:0x559721de9ac0、 size = 7184、prev = 0
  89. チャンク[7]:00 00 00 00 00 00 00 00 11 20 00 00 00 00 00 00 00
  90. チャンク[7]:0x559721deb6d0、 size = 8208、prev = 0
  91. チャンク[8]:10 20 00 00 00 00 00 00 00 10 24 00 00 00 00 00 00
  92. チャンク[8]:0x559721ded6e0、 size = 9231、prev = 8208
  93. チャンク[9]:00 00 00 00 00 00 00 00 11 28 00 00 00 00 00 00
  94. チャンク[9]:0x559721DEFAF0、 size = 10256、prev = 0

その結果、図に示すように、Mallocによって返されるアドレスの前の16バイトが割り当てられたメモリサイズを表すことができることがわかります。

上記は、自由なメモリへの呼びかけの結果であることに注意してください。ただし、Mallocは8バイトのみを使用して、割り当てられたメモリサイズを表します。それでは、他の8バイトはどういう意味ですか? malloc関数のコメントを見てください:

  1. 1055 /*
  2. 1056 malloc_chunk詳細:
  3. 1057
  4. 1058(以下には、Colin Plumbによる軽く編集された説明が含まれています。)
  5. 1059
  6. 1060のメモリチャンクは、「境界タグ」メソッドを使用して維持されます 
  7. たとえば、KnuthまたはStandish記載されている1061。 (ポール論文を参照してください
  8. 1062ウィルソンftp://ftp.cs.utexas.edu/pub/garbage/allocsrv.ps for
  9. 1063そのような手法調査。)サイズ 無料のチャンクは両方を保存します
  10. 各チャンク前面1064  最後これにより
  11. 1065断片化されたチャンクを非常に速く大きなチャンク統合します。
  12. 1066サイズフィールドは、チャンクが無料かどうかを表すビットも保持します または 
  13. 使用中の1067
  14. 1068
  15. 1069割り当てられたチャンクは次のようになります
  16. 1070
  17. 1071
  18. 1072 chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  19. 1073 |サイズ 以前のチャンク、unallocated(p clear)|
  20. 1074 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  21. 1075 |サイズ チャンクバイト| a | m | p |
  22. 1076 mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  23. 1077 |ユーザーデータはここから始まります...。
  24. 1078。
  25. 1079。(malloc_usable_size()bytes)。
  26. 1080。 |
  27. 1081 nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  28. 1082 | (サイズ チャンクですが、アプリケーションデータ使用されます)|
  29. 1083 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  30. 1084 |サイズ  次のチャンク、バイト| a | 0 | 1 |
  31. 1085 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  32. 1086
  33. 1087ここで  "かたまり"  ほとんど目的ためにチャンク前面です 
  34. 1088マロックコード、しかし「メム」  返されるポインターです
  35. 1089ユーザー 「nextchunk」  連続チャンク始まりです

これらの16バイトには2つの意味があることがわかります。最初の8バイトは、割り当てられていない以前のスペースにあるバイトの数を示します。最後の8バイトは、mallocによって割り当てられたバイトサイズを示します。無料のコードを介してそれをチェックしてください:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4.  
  5. /**
  6. * PMEM -MEMを印刷します
  7. * @P:印刷を開始するメモリアドレス                                                    
  8. * @bytes:印刷するバイト
  9. *
  10. *返品:何もありません
  11. */
  12. void pmem(void *p、unsigned int bytes)
  13. {
  14. 符号なしのchar *ptr;
  15. 署名されていないint i;
  16.  
  17. ptr =(unsigned char *)p;
  18. for (i = 0; i <bytes; i ++)
  19. {
  20. if(i!= 0)
  21. {
  22. printf( "" );
  23. }
  24. printf( "%02x" 、 *(ptr + i));
  25. }
  26. printf( "\ n" );
  27. }
  28.  
  29. /**
  30. *メイン - ソースコードを確認します
  31. *
  32. *返品:何かが失敗した場合はexit_failure。それ以外の場合はexit_success
  33. */
  34. int main(void)
  35. {
  36. void *p;
  37. int i;
  38. size_t size_of_the_chunk;
  39. size_t size_of_the_previous_chunk;
  40. void *チャンク[10];
  41.  
  42. for (i = 0; i <10; i ++)
  43. {
  44. p = malloc(1024 *(i + 1));
  45. チャンク[i] =(void *)(( char *)p -0x10);
  46. printf( "%p \ n" 、p);
  47. }
  48. free (( char *)(chunks [3]) + 0x10);
  49. free (( char *)(chunks [7]) + 0x10);
  50. for (i = 0; i <10; i ++)
  51. {
  52. p =チャンク[i];
  53. printf( "chunks [%d]:" 、i);
  54. PMEM(P、0x10);
  55. size_of_the_chunk = *((size_t *)(( char *)p + 8))-1;
  56. size_of_the_previous_chunk = *((size_t *)(( char *)p));
  57. printf( "チャンク[%d]:%p、size =%li、prev =%li \ n"
  58. i、p、size_of_the_chunk、size_of_the_previous_chunk);
  59. }
  60. return (exit_success);
  61. }
  62.  
  63. コンパイルして出力を実行します:
  64. root@3e8650948c0c:/ubuntu#gcc test.c -oテスト
  65. root@3e8650948c0c:/ubuntu#./test
  66. 0x55FBEBF20260
  67. 0x55FBEBF20A80
  68. 0x55FBEBF21290
  69. 0x55FBEBF21EA0
  70. 0x55FBEBF22EB0
  71. 0x55FBEBF242C0
  72. 0x55FBEBF25AD0
  73. 0x55FBEBF276E0
  74. 0x55FBEBF296F0
  75. 0x55FBEBF2BB00
  76. チャンク[0]:00 00 00 00 00 00 00 00 11 04 00 00 00 00 00 00
  77. チャンク[0]:0x55FBEBF20250、 size = 1040、prev = 0
  78. チャンク[1]:00 00 00 00 00 00 00 00 11 08 00 00 00 00 00 00
  79. チャンク[1]:0x55FBEBF20A70、 size = 2064、prev = 0
  80. チャンク[2]:00 00 00 00 00 00 00 00 11 0C 00 00 00 00 00 00
  81. チャンク[2]:0x55fbebf21280、 size = 3088、prev = 0
  82. チャンク[3]:00 00 00 00 00 00 00 00 11 10 00 00 00 00 00 00 00
  83. チャンク[3]:0x55fbebf21e90、 size = 4112、prev = 0
  84. チャンク[4]:10 10 00 00 00 00 00 00 00 10 14 00 00 00 00 00 00
  85. チャンク[4]:0x55fbebf22ea0、 size = 5135、prev = 4112
  86. チャンク[5]:00 00 00 00 00 00 00 00 11 18 00 00 00 00 00 00
  87. チャンク[5]:0x55fbebf242b0、 size = 6160、prev = 0
  88. チャンク[6]:00 00 00 00 00 00 00 00 11 1C 00 00 00 00 00 00
  89. チャンク[6]:0x55fbebf25ac0、 size = 7184、prev = 0
  90. チャンク[7]:00 00 00 00 00 00 00 00 11 20 00 00 00 00 00 00 00
  91. チャンク[7]:0x55fbebf276d0、 size = 8208、prev = 0
  92. チャンク[8]:10 20 00 00 00 00 00 00 00 10 24 00 00 00 00 00 00
  93. チャンク[8]:0x55fbebf296e0、 size = 9231、prev = 8208
  94. チャンク[9]:00 00 00 00 00 00 00 00 11 28 00 00 00 00 00 00
  95. チャンク[9]:0x55fbebf2baf0、 size = 10256、prev = 0

プログラムコードでは、3と7のデータブロックのスペースが無料であるため、最初の8バイトと4の8バイトがすべて0ではありません。他のものとは異なり、以前のデータブロックが割り当てられていないサイズを示します。また、4ブロックと8ブロックの最後の8バイトを他のブロックと同様に1つ追加する必要はないことに注意することもできます。 Mallocは、以前のデータブロックが割り当てられているかどうかの兆候として1を追加するために使用されると結論付けることができます。 1を追加すると、以前のデータブロックが割り当てられていることを意味します。したがって、以前のプログラムコードは次の形式に変更できます。

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4.  
  5. /**
  6. * PMEM -MEMを印刷します
  7. * @P:印刷を開始するメモリアドレス                                                    
  8. * @bytes:印刷するバイト
  9. *
  10. *返品:何もありません
  11. */
  12. void pmem(void *p、unsigned int bytes)
  13. {
  14. 符号なしのchar *ptr;
  15. 署名されていないint i;
  16.  
  17. ptr =(unsigned char *)p;
  18. for (i = 0; i <bytes; i ++)
  19. {
  20. if(i!= 0)
  21. {
  22. printf( "" );
  23. }
  24. printf( "%02x" 、 *(ptr + i));
  25. }
  26. printf( "\ n" );
  27. }
  28.  
  29. /**
  30. *メイン -正しいチェック更新
  31. *
  32. *返品:何かが失敗した場合はexit_failure。それ以外の場合はexit_success
  33. */
  34. int main(void)
  35. {
  36. void *p;
  37. int i;
  38. size_t size_of_the_chunk;
  39. size_t size_of_the_previous_chunk;
  40. void *チャンク[10];
  41. Char prev_used;
  42.  
  43. for (i = 0; i <10; i ++)
  44. {
  45. p = malloc(1024 *(i + 1));
  46. チャンク[i] =(void *)(( char *)p -0x10);
  47. }
  48. free (( char *)(chunks [3]) + 0x10);
  49. free (( char *)(chunks [7]) + 0x10);
  50. for (i = 0; i <10; i ++)
  51. {
  52. p =チャンク[i];
  53. printf( "chunks [%d]:" 、i);
  54. PMEM(P、0x10);
  55. size_of_the_chunk = *((size_t *)(( char *)p + 8));
  56. prev_used = size_of_the_chunk&1;
  57. size_of_the_chunk - = prev_used;
  58. size_of_the_previous_chunk = *((size_t *)(( char *)p));
  59. printf( "チャンク[%d]:%p、size =%li、prev(%s)=%li \ n"
  60. I、P、size_of_the_chunk、
  61. (prev_used? "割り当て" "unallocated" )、size_of_the_previous_chunk);
  62. }
  63. return (exit_success);
  64. }
  65. コンパイルして出力を実行します:
  66. root@3e8650948c0c:/ubuntu#gcc test.c -oテスト
  67. root@3e8650948c0c:/ubuntu#./test
  68. チャンク[0]:00 00 00 00 00 00 00 00 11 04 00 00 00 00 00 00
  69. Chunks [0]:0x56254f888250、 size = 1040、prev(割り当て)= 0
  70. チャンク[1]:00 00 00 00 00 00 00 00 11 08 00 00 00 00 00 00
  71. チャンク[1]:0x56254f888660、 size = 2064、prev(割り当て)= 0
  72. チャンク[2]:00 00 00 00 00 00 00 00 11 0C 00 00 00 00 00 00
  73. チャンク[2]:0x56254f888e70、 size = 3088、prev(forcated)= 0
  74. チャンク[3]:00 00 00 00 00 00 00 00 11 04 00 00 00 00 00 00
  75. チャンク[3]:0x56254f889a80、 size = 1040、prev(割り当て)= 0
  76. チャンク[4]:00 0c 00 00 00 00 00 00 10 14 00 00 00 00 00 00
  77. チャンク[4]:0x56254f88aa90、 size = 5136、prev(unallocated)= 3072
  78. チャンク[5]:00 00 00 00 00 00 00 00 11 18 00 00 00 00 00 00
  79. チャンク[5]:0x56254f88bea0、 size = 6160、prev(forcated)= 0
  80. チャンク[6]:00 00 00 00 00 00 00 00 11 1C 00 00 00 00 00 00
  81. チャンク[6]:0x56254f88d6b0、 size = 7184、prev(割り当て)= 0
  82. チャンク[7]:00 00 00 00 00 00 00 00 11 20 00 00 00 00 00 00 00
  83. チャンク[7]:0x56254f88f2c0、 size = 8208、prev(割り当て)= 0
  84. チャンク[8]:10 20 00 00 00 00 00 00 00 10 24 00 00 00 00 00 00
  85. チャンク[8]:0x56254f8912d0、 size = 9232、prev(unallocated)= 8208
  86. チャンク[9]:00 00 00 00 00 00 00 00 11 28 00 00 00 00 00 00
  87. チャンク[9]:0x56254f8936e0、 size = 10256、prev(割り当て)= 0

ヒープスペースは上方に成長していますか?

コードで確認してください:

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4.  
  5. /**
  6. *メイン - プログラムの移動
  7. *
  8. *返品:何かが失敗した場合はexit_failure。それ以外の場合はexit_success
  9. */
  10. int main(void)
  11. {
  12. int i;
  13.  
  14. 書き込み(1、 "start \ n" 、6);
  15. malloc(1);
  16. getchar();
  17. 書き込み(1、 "loop \ n" 、5);
  18. for (i = 0; i <0x25000 / 1024; i ++)
  19. {
  20. Malloc(1024);
  21. }
  22. 書き込み(1、 "end \ n" 、4);
  23. getchar();
  24. return (exit_success);
  25. }
  26. パーツサマリー出力をコンパイルして実行します。
  27. root@3e8650948c0c:/ubuntu#gcc test.c -oテスト
  28. root@3e8650948c0c:/ubuntu#strace ./test
  29. execve( "./Test"、["./Test"]、0x7ffe0d7cbd80/ * 10 vars */) = 0
  30. BRK( null )= 0x555A2428F000
  31. アクセス( "/etc/ld.so.nohwcap"、f_ok)= -1 enoent(そのようなファイルディレクトリなし
  32. アクセス( "/etc/ld.so.preload"、r_ok)= -1 enoent(そのようなファイルまたはディレクトリなし
  33. openat(at_fdcwd、 "/etc/ld.so.cache"、o_rdonly | o_cloexec)= 3
  34. fstat(3、{st_mode = s_ifreg | 0644、st_size = 13722、...})= 0
  35. mmap( null 、13722、prot_read、map_private、3、0)= 0x7f6423455000
  36. 閉じる(3)= 0
  37. アクセス( "/etc/ld.so.nohwcap"、f_ok)= -1 enoent(そのようなファイルディレクトリなし
  38. openat(at_fdcwd、 "/lib/x86_64-linux-gnu/libc.so.6"、o_rdonly | o_cloexec)= 3
  39. read (3、 "\ 177 Elf \ 2 \ 1 \ 1 \ 3 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 0 \ 3 \ 0> \ 0 \ 1 \ 0 \ 0 \ 0 \ 260 \ 34 \ 2 \
  40. fstat(3、{st_mode = s_ifreg | 0755、st_size = 2030544、...})= 0
  41. mmap( null 、8192、prot_read | prot_write、map_private | map_anonymous、-1、0)= 0x7f6423453000
  42. mmap( null 、4131552、prot_read | prot_exec、map_private | map_denywrite、3、0)= 0x7f6422e41000
  43. mprotect(0x7f6423028000、2097152、prot_none)= 0
  44. MMAP(0x7F6423228000、24576、prot_read | prot_write、map_private | map_fixed | map_denywrite、3、0x1e7000)= 0x7f6423228000
  45. MMAP(0x7F642322E000、15072、prot_read | prot_write、map_private | map_fixed | map_anonymous、-1、0)= 0x7f642322e000
  46. 閉じる(3)= 0
  47. arch_prctl(arch_set_fs、0x7f64234544c0)= 0
  48. mprotect(0x7F6423228000、16384、prot_read)= 0
  49. mprotect(0x555A22F5F000、4096、prot_read)= 0
  50. mprotect(0x7F6423459000、4096、prot_read)= 0
  51. Munmap(0x7F6423455000、13722)= 0
  52. 書き込み(1、 "start \ n" 、6start
  53. )= 6
  54. BRK( null )= 0x555A2428F000
  55. BRK(0x555A242B0000)= 0x555A242B0000
  56. fstat(0、{st_mode = s_ifchr | 0620、st_rdev = makedev(136、0)、...})= 0
  57. 読む(0、
  58. "\ n" 、1024)= 1
  59. 書き込み(1、 "loop \ n" 、5loop
  60. )= 5
  61. BRK(0x555A242D1000)= 0x555A242D1000
  62. BRK(0x555A242F2000)= 0x555A242F2000
  63. BRK(0x555A24313000)= 0x555A24313000
  64. BRK(0x555A24334000)= 0x555A24334000
  65. BRK(0x555A24355000)= 0x555A24355000
  66. BRK(0x555A24376000)= 0x555A24376000
  67. BRK(0x555A24397000)= 0x555A24397000
  68. BRK(0x555A243B8000)= 0x555A243B8000
  69. BRK(0x555A243D9000)= 0x555A243D9000
  70. BRK(0x555A243FA000)= 0x555A243FA000

ヒープスペースが上方に成長していることがわかります。

ランダム化アドレススペースレイアウト

最初から今まで、多くのプロセスが実行されています。対応するプロセスのマップを見ると、各プロセスのヒープの開始アドレスと実行可能ファイルプログラムの最終アドレスがすぐに隣接しておらず、ギャップは毎回異なることがわかりました。

  1. [3718]:01195000 - 00602000 = B93000
  2. [3834]:024D6000 - 00602000 = 1ED4000
  3. [4014]:00E70000 - 00602000 = 86E000
  4. [4172]:01314000 –00602000 = D12000
  5. [7972]:00901000 - 00602000 = 2FF000

この違いがランダムであることがわかります。 FS/BINFMT_ELF.Cのソースコードを確認してください

  1. if(( current- > flags&pf_randomize)&&(randomize_va_space> 1)){
  2. current- > mm-> brk = current- > mm-> start_brk =
  3. arch_randomize_brk( current- > mm);
  4. #ifdef compat_brk_randomized
  5. 現在- > brk_randomized = 1;
  6. #終了
  7. }
  8. //現在- > mm-> brkは現在のプロセスプログラムの中断のアドレスです

arch_randomize_brk関数はarch/x86/kernel/process.cにあります

  1. 符号なしの長いarch_randomize_brk(struct mm_struct *mm)
  2. {
  3. unsigned long range_end = mm-> brk + 0x02000000;
  4. return randomize_range(mm-> brk、range_end、0)? :mm-> brk;
  5. }

drivers/char/random.cのランダム化_range関数

  1. /*
  2. * RANDIOMIZE_RANGE()、そのように
  3. *
  4. * [...... <Range> ....]
  5. *終了を開始します 
  6. *
  7. * a <range> with  サイズ リターンから始まる「レン」内部あります
  8. * [start、 end ]定義されている領域ですが、それ以外の場合はランダム化されます
  9. */
  10. 符号なしの長い
  11. andimize_range(符号なしのロングスタート、符号なしの長い終了、符号なし長いレン)
  12. {
  13. 符号なしの長距離= end -len -start;
  14.  
  15. if( end <= start + len)
  16. 0を返します
  17. return page_align(get_random_int()%range + start);
  18. }

上記の違いは、実際には0-0x02000000の乱数であることがわかります。このテクノロジーはASLRと呼ばれます(アドレススペースレイアウトランダム化)。これは、ハッカーが攻撃を効果的に妨げる可能性のある仮想メモリのスタックスペースの位置をランダムに配置するコンピューターセキュリティテクノロジーです。上記の分析により、メモリ分布図は次のように描画できます。

Malloc(0)はどうなりましたか?

malloc(0)が呼び出された場合、コードは次のとおりです。

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4.  
  5. /**
  6. * PMEM -MEMを印刷します
  7. * @P:印刷を開始するメモリアドレス                                                    
  8. * @bytes:印刷するバイト
  9. *
  10. *返品:何もありません
  11. */
  12. void pmem(void *p、unsigned int bytes)
  13. {
  14. 符号なしのchar *ptr;
  15. 署名されていないint i;
  16.  
  17. ptr =(unsigned char *)p;
  18. for (i = 0; i <bytes; i ++)
  19. {
  20. if(i!= 0)
  21. {
  22. printf( "" );
  23. }
  24. printf( "%02x" 、 *(ptr + i));
  25. }
  26. printf( "\ n" );
  27. }
  28.  
  29. /**
  30. *メイン - プログラムの移動
  31. *
  32. *返品:何かが失敗した場合はexit_failure。それ以外の場合はexit_success
  33. */
  34. int main(void)
  35. {
  36. void *p;
  37. size_t size_of_the_chunk;
  38. Char prev_used;
  39.  
  40. p = malloc(0);
  41. printf( "%p \ n" 、p);
  42. PMEM(( char *)p -0x10、0x10);
  43. size_of_the_chunk = *((size_t *)(( char *)p -8));
  44. prev_used = size_of_the_chunk&1;
  45. size_of_the_chunk - = prev_used;
  46. printf( "chunk size =%li bytes \ n" 、size_of_the_chunk);
  47. return (exit_success);
  48. }
  49. コンピレーションとランニング出力は次のとおりです。
  50. root@3e8650948c0c:/ubuntu#gcc test.c -oテスト
  51. root@3e8650948c0c:/ubuntu#./test
  52. 0x564ECE64B260
  53. 00 00 00 00 00 00 00 00 00 21 00 00 00 00 00 00 00
  54. チャンクサイズ= 32バイト

Malloc(0)は、前述の16バイトヘッダーを含む32バイトを実際に使用していることがわかります。ただし、Malloc(0)が異なる結果出力を持つ場合があり、nullを返す場合があります。

  1. マンマロック
  2. nullは、サイズmalloc ()へのコールを成功させることによって返される場合もあります ゼロ

動作環境

  1. サンプルコードは主に2つの環境で実行されます。
  2. Ubuntu 16.04
  3. gcc(ubuntu 7.4.0-1ubuntu1〜16.04〜ppa1)7.4.0
  4.   
  5. Ubuntu 18.04 Docker
  6. gcc(ubuntu 7.4.0-1ubuntu1〜18.04.1)7.4.0

この記事は、この一連の記事から翻訳され、抽出され、あなた自身の理解と組み合わされました。コードは自分で練習されています。時間がある場合は、元の英語リンクを直接読むことができます。

仮想メモリをハッキング:C Strings& /Proc

仮想メモリをハック:VM図を描きます

仮想メモリをハック:Malloc、Heap、The Program Break

<<:  クラウド コンピューティングの簡単な歴史 (完全版)

>>:  クラウドコンピューティングは社会の将来にとって重要な要素となるでしょうか?

推薦する

KubeDL が CNCF Sandbox に参加し、AI 業界のクラウドネイティブ化を加速

2021 年 6 月 23 日、Cloud Native Computing Foundation ...

WeChatマーケティングを正しく理解するためのWeChatモーメント運用の10の経験

前回「企業向けWeChatパブリックプラットフォームサブスクリプションアカウント運用の秘訣11選」を...

コンテナ技術の発展予測

コンテナ市場が進化するにつれて、考慮すべき 4 つの予測があります。 Docker テクノロジーは ...

オンライン広告トラフィックの 3 分の 1 は、機械によって生成された偽のトラフィックです。あなたの上司はこれを知っていますか?

統計によると、インターネット上の広告の 85% は効果がなく、誰も見ていないそうです。現在、これらの...

製品ハイライト|Anweisi データベース監査 - バインディング変数監査

機能紹介データセキュリティ管理エキスパート - コアデータを保護し、ネットワークセキュリティを防御し...

今週のニュースレビュー: 百度のアルゴリズムが再び更新、360度検索が人気に、土豆が上場廃止

1. 百度のアルゴリズムの最新アップグレードにより、検索における低品質の結果の表示がさらに削減される...

リンクの偽装と百度の本物と偽物のスパイダー

2018年最もホットなプロジェクト:テレマーケティングロボットがあなたの参加を待っています以前、グル...

レンタカー業界で「無料」のマーケティング戦略を活用する方法を学ぶ

無料のマーケティング戦略は、現実でもインターネットでも非常に実用的で効果的です。私が従事しているレン...

瓜子中古車の新たな電子商取引と新たなトラブル

中古品取引市場の急速な拡大に伴い、さまざまな中古電子商取引プラットフォームが雨後の筍のように出現し、...

彼は言った:SEOの専門家はアヒルを育てるために田舎に戻った

建国記念日が終わったら、また一生懸命働き始めなければなりません。これは、ほとんどの SEO 担当者が...

カタール サーバー: zenlayer、30% 割引、ドーハ データ センター、10Gbps 帯域幅、カスタム構成リソース

カタールはペルシャ湾の南西海岸に位置し、世界有数の石油・天然ガス生産地域であり、非常に豊かな国です。...

gamedatainc-1g メモリ/80g ハードディスク/1T トラフィック/月額 4.61 ドル (IP あたり 0.25 ドル)

gamedatainc の VPS プロモーション、33% オフの割引コード: LET、年間支払いで...

マルチクラウドネットワークはよりフラットになる

マルチクラウドはエンドツーエンドでフラット化されます。次の 10 年に向けて、クラウド プラットフォ...

ecvps5.95USD/月512MB RAM 完全管理型 VPS

ecvpsは2009年に設立されたVPS事業です。社長は香港出身です。ネットユーザーによると、社長は...