[[351558]] この記事はWeChatの公開アカウント「Programmer Cat Master」から転載したもので、著者はProgrammer Cat Masterです。この記事を転載する場合は、Programmer Catの公式アカウントまでご連絡ください。 正直に言います。もう偽りはしません。実は私は一日中一生懸命働き、家に帰って夜遅くまで公開アカウントを編集しなければならないプログラマーです。皆さんが積極的に転送して、私のKPI(夢)である1,000回の視聴を達成してくれることを願っています。ありがとう! えーと、ちょっと話がそれてしまいました。以下はProgram Catのナンセンスです。私が Program Cat よりも人気があることを証明するために、私に少し顔を見せて、最後までスクロールして「読む」または「いいね」をクリックしてください。ありがとう! /proc ファイルシステムを通じて仮想メモリを調べる /proc ファイルシステムを通じて実行中のプロセスの文字列の仮想メモリ アドレスを見つけ、このメモリ アドレスの内容を変更することで文字列の内容を変更します。これにより、仮想メモリの概念をより深く理解できるようになります。その前に、まずは仮想メモリの定義を紹介しましょう! 仮想メモリ 仮想メモリは、コンピュータのソフトウェアとハードウェアの間に実装されるメモリ管理テクノロジです。プログラムが使用するメモリ アドレス (仮想アドレス) をコンピュータ メモリ内の物理アドレスにマッピングします。仮想メモリにより、アプリケーションはメモリ空間を管理する面倒な作業から解放され、メモリの分離によってもたらされるセキュリティが向上します。仮想メモリ アドレスは通常、連続したアドレス空間であり、オペレーティング システムのメモリ管理モジュールによって制御されます。ページ フォールト割り込みがトリガーされると、実際の物理メモリがページング テクノロジを使用して仮想メモリに割り当てられます。さらに、64 ビット マシンの仮想メモリのスペース サイズは実際の物理メモリのサイズをはるかに超えるため、プロセスは物理メモリ サイズよりも多くのメモリ スペースを使用できます。 仮想メモリについて詳しく説明する前に、いくつかの重要なポイントがあります。 - 各プロセスには独自の仮想メモリがある
- 仮想メモリのサイズはシステムアーキテクチャによって異なります。
- オペレーティング システムによって仮想メモリの管理方法は異なりますが、ほとんどのオペレーティング システムの仮想メモリ構造は次のとおりです。
仮想メモリ.png 上の図は特に詳細なメモリ管理図ではありません。実際は高アドレスにカーネル空間などがあるのですが、これはこの記事の主題ではありません。図から、上位アドレスにはコマンドラインパラメータと環境変数が格納され、その後にスタック領域、ヒープ領域、実行可能プログラムが続くことがわかります。スタック スペースは下方向に拡張され、ヒープ スペースは上方向に拡張されます。ヒープ領域は malloc を使用して割り当てる必要があり、動的に割り当てられるメモリの一部です。 まず、簡単な C プログラムを通じて仮想メモリについて調べてみましょう。 - #include <stdlib.h>
- #include <stdio.h>
- #include <文字列.h>
-
- /**
- * main - strdup を使用して文字列のコピーを作成します。strdup は malloc を使用してスペースを割り当てます。
- * 新しいスペースのアドレスを返します。このアドレス空間は、フリーアドレスを使用して外部に解放する必要があります。
- *
- *戻り値: malloc が失敗した場合は EXIT_FAILURE を返します。それ以外の場合はEXIT_SUCCESS
- */
- intメイン(void)
- {
- 文字*s;
-
- s = strdup( "test_memory" );
- s == NULL の場合
- {
- fprintf(stderr, "mallocでメモリを割り当てることができません\n" );
- 戻り値(EXIT_FAILURE);
- }
- printf( "%p\n" , (void *)s);
- 戻り値(EXIT_SUCCESS);
- }
-
- コンパイルして実行します: gcc -Wall -Wextra -pedantic -Werror main.c -o test; 。/テスト
- 出力: 0x88f010
私のマシンは 64 ビット マシンです。プロセスの仮想メモリの上位アドレスは 0xffffffffffffffff で、下位アドレスは 0x0 です。 0x88f010 は 0xffffffffffffffffff よりはるかに小さいため、コピーされた文字列のアドレス (ヒープ アドレス) はメモリの下位アドレスに近いと大まかに推測でき、これは /proc ファイル システムを通じて確認できます。 ls /proc ディレクトリには多くのファイルが表示されます。ここでは主に/proc/[pid]/memと/proc/[pid]/mapsに焦点を当てます。 メモリとマップ - 男性プロセス
-
- /proc/[pid]/mem
- このファイルは、 open (2)、 read (2)、 lseek (2)を通じてプロセスのメモリのページにアクセスするために使用できます。
-
- /proc/[pid]/マップ
- 現在マップされているメモリ領域とそのアクセス権限を含むファイル。
- mmap(2)を参照 メモリ マッピングに関する詳細情報。
-
- ファイルの形式は次のとおりです:
-
- アドレス 権限 オフセット デバイス inode パス名
- 00400000-00452000 r-xp 00000000 08:02 173521 /usr/bin/dbus-daemon
- 00651000-00652000 r
- 00652000-00655000 rw-p 00052000 08:02 173521 /usr/bin/dbus-daemon
- 00e03000-00e24000 rw-p 00000000 00:00 0 [ヒープ]
- 00e24000-011f7000 rw-p 00000000 00:00 0 [ヒープ]
- ...
- 35b1800000-35b1820000 r-xp 00000000 08:02 135522 /usr/lib64/ld-2.15.so
- 35b1a1f000-35b1a20000 r
- 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
- 35b1fac000-35b1fb0000 r
- 35b1fb0000-35b1fb2000 rw-p 001b0000 08:02 135870 /usr/lib64/libc-2.15.so
- ...
- f2c6ff8c000-7f2c7078c000 rw-p 00000000 00:00 0 [スタック:986]
- ...
- 7fffb2c0d000-7fffb2c2e000 rw-p 00000000 00:00 0 [スタック]
- 7fffb2d48000-7fffb2d49000 r-xp 00000000 00:00 0 [vdso]
-
- アドレスフィールドはアドレス空間である マッピングが占めるプロセスにおいて。
- 権限フィールドはセットです 権限:
-
- r =読む
- w = 書く
- x =実行
- s = 共有
- p = プライベート(書き込み時にコピー)
-
- オフセット フィールドは、ファイルまたはその他の場所へのオフセットです。
- dev はデバイス (メジャー:マイナー)です。 inode はそのデバイス上のinodeです。 0は
- メモリ領域にiノードが関連付けられていないこと、
- そうであるように BSS (初期化されていないデータ)付き。
-
- パス名フィールドは通常、マッピングをバックアップするファイルになります。
- ELFファイルの場合、オフセットフィールドで簡単に調整できます。
- ELF プログラム ヘッダーのOffset フィールドを確認します( readelf -l)。
-
- 追加の便利な疑似パスがあります:
-
- [スタック]
- 初期プロセス(メイン スレッドとも呼ばれる) のスタック。
-
- [stack:<tid>] (Linux 3.4 以降)
- スレッドのスタック ( <tid>はスレッドID)。
- これは/proc/[pid]/task/[tid]/ パスに対応します。
-
- [vdso] 仮想動的リンク共有オブジェクト。
-
- [heap] プロセスのヒープ。
-
- パス名フィールドが空白の場合、これはmmap(2)関数を介して取得された匿名マッピングです。
- がある 簡単に調整する方法がない
- これを gdb(1)、strace(1)などを通じて実行しない限り、プロセスのソースに戻すことはできません。
-
- Linux 2.0では パス名を指定するフィールドがありません。
mem ファイルを使用すると、プロセス全体のメモリ ページにアクセスして変更できます。マップを通じて、アドレスやアクセス許可のオフセットなど、プロセスによって現在マップされているメモリ領域を確認できます。マップから、ヒープ領域は低いアドレスにあり、スタック領域は高いアドレスにあることがわかります。マップから、ヒープのアクセス許可が rw (書き込み可能) であることがわかります。そのため、ヒープ アドレスを通じて前のサンプル プログラムの文字列のアドレスを見つけ、mem ファイルの対応するアドレスの内容を変更して、文字列の内容を変更できます。プログラム: - #include <stdlib.h>
- #include <stdio.h>
- #include <文字列.h>
- #include <unistd.h>
-
- /**
- * main - strdupを使用して 新しい文字列を作成し、永遠にループします
- *
- *戻り値: malloc が失敗した場合は EXIT_FAILURE を返します。他は戻ってこない
- */
- intメイン(void)
- {
- 文字*s;
- 符号なし long int i;
-
- s = strdup( "test_memory" );
- s == NULL の場合
- {
- fprintf(stderr, "mallocでメモリを割り当てることができません\n" );
- 戻り値(EXIT_FAILURE);
- }
- 私 = 0;
- ながら
- {
- printf( "[%lu] %s (%p)\n" , i, s, (void *)s);
- 睡眠(1);
- 私は++;
- }
- 戻り値(EXIT_SUCCESS);
- }
- コンパイルして実行します: gcc -Wall -Wextra -pedantic -Werror main.c -o loop; 。/ループ
- 出力:
- [0] テストメモリ (0x21dc010)
- [1] テストメモリ(0x21dc010)
- [2] テストメモリ(0x21dc010)
- [3] テストメモリ(0x21dc010)
- [4] テストメモリ(0x21dc010)
- [5] テストメモリ(0x21dc010)
- [6] テストメモリ(0x21dc010)
- ...
ここで、/proc ファイルシステムを通じて文字列の場所を見つけてその内容を変更するスクリプトを記述すると、対応する出力も変更されます。 まずプロセスのプロセスIDを見つける - 追伸 | grep ./ループ | grep -v grep
- zjucad 2542 0.0 0.0 4352 636 ポイント/3 S+ 12:28 0:00 ./loop
2542 はループ プログラムのプロセス番号であり、cat /proc/2542/maps を実行すると取得できます。 - 00400000-00401000 r-xp 00000000 08:01 811716 /home/zjucad/wangzhiqiang/ループ
- 00600000-00601000 r
- 00601000-00602000 rw-p 00001000 08:01 811716 /home/zjucad/wangzhiqiang/ループ
- 021dc000-021fd000 rw-p 00000000 00:00 0 [ヒープ]
- 7f2adae2a000-7f2adafea000 r-xp 00000000 08:01 8661324 /lib/x86_64-linux-gnu/libc-2.23.so
- 7f2adafea000-7f2adb1ea000
- 7f2adb1ea000-7f2adb1ee000 r
- 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
- 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 [スタック]
- 7ffd51bdd000-7ffd51be0000 r
- 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
- '' '
- ヒープ内の文字列の最初の出現箇所を見つけて置換します
- プロセスの
-
- 使用方法: ./read_write_heap.py PID search_string replace_by_string
- どこ:
- -PIDは対象プロセスのPIDです
- - search_stringは上書きしたいASCII文字列です
- - replace_by_stringは置換したいASCII文字列です 交換する
- 検索文字列と
- '' '
-
- インポートシステム
-
- デフprint_usage_and_exit():
- print( '使用法: {} pid search write' .format(sys.argv[0]))
- sys.exit(1)
-
- #使用方法を確認する
- len(sys.argv) != 4の場合:
- 印刷使用法と終了()
-
- # argsからpidを取得する
- pid = int (sys.argv[1])
- pid <= 0 の場合:
- 印刷使用法と終了()
- 検索文字列 = str(sys.argv[2])
- 検索文字列 == ""の場合:
- 印刷使用法と終了()
- write_string = str(sys.argv[3])
- 検索文字列 == ""の場合:
- 印刷使用法と終了()
-
- #プロセスのマップとメモリファイルを開く
- maps_filename = "/proc/{}/maps" .format(pid)
- print( "[*] マップ: {}" .format(maps_filename))
- mem_filename = "/proc/{}/mem" .format(pid)
- print( "[*] mem: {}" .format(mem_filename))
-
- # マップファイルを開いてみる
- 試す:
- maps_file = open ( '/proc/{}/maps' .format(pid), 'r' )
- IOErrorを除き、 e:
- print( "[エラー] ファイル {} を開けません:" .format(maps_filename))
- print( "I/Oエラー({}): {}" .format(e.errno, e.strerror))
- sys.exit(1)
-
- maps_file内の行:
- sline = 行を分割します( ' ' )
- #ヒープが見つかったかどうか確認する
- sline[-1][:-1] != "[heap]"の場合:
- 続く
- print( "[*] [ヒープ] が見つかりました:" )
-
- # 行を解析する
- アドレス = 行[0]
- パーマ = sline[1]
- オフセット = sline[2]
- デバイス = sline[3]
- inode = sline[4]
- パス名 = sline[-1][:-1]
- print( "\tpathname = {}" .format(pathname))
- print( "\taddresses = {}" .format(addr))
- print( "\tpermissions = {}" .format(perm))
- print( "\toffset = {}" .format(offset))
- print( "\tinode = {}" .format(inode))
-
- #あるかどうか確認する 読む 書き込み権限
- perm[0] != 'r'の場合 またはperm[1] != 'w' :
- print( "[*] {} には読み取り/書き込み権限がありません" .format(pathname))
- maps_file.close ( ) 関数
- 終了(0)
-
- # 始めよう 終わり 仮想メモリ内のヒープの
- addr = addr.split( "-" )
- if len(addr) != 2: # 誰も信用しないでください。OSでさえ信用しないでください :)
- print( "[*] アドレスの形式が間違っています" )
- maps_file。近い()
- 終了(1)
- addr_start = int (addr[0], 16)
- addr_end = int (addr[1], 16)
- print( "\tAddr 開始 [{:x}] | 終了 [{:x}]" .format(addr_start, addr_end))
-
- #開ける そして メモリを読む
- 試す:
- mem_file = open (mem_filename, 'rb+' )を実行します。
- IOErrorを除き、 e:
- print( "[エラー] ファイル {} を開けません:" .format(mem_filename))
- print( "I/Oエラー({}): {}" .format(e.errno, e.strerror))
- maps_file.close ( ) 関数
- 終了(1)
-
- #ヒープ読み取り
- mem_file.seek(addr_start)
- ヒープ = mem_file。読み取り(addr_end - addr_start)
-
- # 文字列を検索
- 試す:
- i = ヒープ。インデックス(バイト(検索文字列、 "ASCII" ))
- 例外を除く:
- print( "'{}' が見つかりません" .format(search_string))
- maps_file。近い()
- mem_file.close ( )関数
- 終了(0)
- print( "[*] {:x} に '{}' が見つかりました" .format(search_string, i))
-
- # 新しい文字列を書き込む
- print( "[*] {:x} に '{}' を書き込み中" .format(write_string, addr_start + i))
- mem_file.seek(addr_start + i)
- mem_file.write(bytes(write_string, "ASCII" ))
-
- #ファイルを閉じる
- maps_file。近い()
- mem_file.close ( )関数
-
- #がある この例ではヒープは1つだけ
- 壊す
このPythonスクリプトを実行する - zjucad@zjucad-ONDA-H110-MINI-V3-01:~/wangzhiqiang$ sudo ./loop.py 2542 test_memory test_hello
- [*] マップ: /proc/2542/maps
- [*] メモリ: /proc/2542/mem
- [*] [ヒープ] が見つかりました:
- パス名 = [ヒープ]
- アドレス = 021dc000-021fd000
- 権限 = rw-p
- オフセット = 00000000
- iノード = 0
- アドレス開始 [21dc000] |終了[21fd000]
- [*] 'test_memory'が見つかりました 10時
- [*] 'test_hello'を書いています 21dc010で
文字列出力の内容も変更されました - [633] テストメモリ (0x21dc010)
- [634] テストメモリ (0x21dc010)
- [635] テストメモリ (0x21dc010)
- [636] テストメモリ (0x21dc010)
- [637] テストメモリ (0x21dc010)
- [638] テストメモリ (0x21dc010)
- [639] テストメモリ (0x21dc010)
- [640] テスト_ハロイ (0x21dc010)
- [641] テスト_ハロイ (0x21dc010)
- [642] テスト_ハロイ (0x21dc010)
- [643] テスト_ハロイ (0x21dc010)
- [644] テスト_ハロイ (0x21dc010)
- [645] テスト_ハロイ (0x21dc010)
実験は成功した。 実践を通して仮想メモリ空間配分図を描く メモリ空間配分図を再度リストします 基本的に仮想メモリの空間分布については誰もが多かれ少なかれ知っていますが、それを検証する方法については後述します。 スタックスペース まず、スタック スペースの場所を確認します。 C のローカル変数はスタック空間に格納され、malloc によって割り当てられたメモリはヒープ空間に格納されることは誰もが知っているので、ローカル変数のアドレスと malloc の戻りメモリ アドレスを出力することで、仮想空間全体におけるスタック空間の位置を確認できます。 - #include <stdlib.h>
- #include <stdio.h>
- #include <文字列.h>
-
- /**
- * main -さまざまな要素の印刷場所
- *
- *戻り値: 何かが失敗した場合は EXIT_FAILURE を返します。それ以外の場合はEXIT_SUCCESS
- */
- intメイン(void)
- {
- 整数a;
- void *p;
-
- printf( "aのアドレス: %p\n" , (void *)&a);
- __p = malloc(98);
- (p == NULL )の場合
- {
- fprintf(stderr, "mallocできません\n" );
- 戻り値(EXIT_FAILURE);
- }
- printf( "ヒープ内に割り当てられたスペース: %p\n" , p);
- 戻り値(EXIT_SUCCESS);
- }
- コンパイルして実行します: gcc -Wall -Wextra -pedantic -Werror main.c -o test; 。/テスト
- 出力:
- アドレス: 0x7ffedde9c7fc
- 割り当てられたスペース ヒープ内: 0x55ca5b360670
結果から、図に示すように、ヒープ アドレス空間がスタック アドレス空間の下にあることがわかります。 実行可能プログラム 実行可能プログラムも仮想メモリ内にあります。メイン関数のアドレスを出力し、それをスタック アドレスと比較することで、スタック アドレスに対する実行可能プログラム アドレスの分布を知ることができます。 - #include <stdlib.h>
- #include <stdio.h>
- #include <文字列.h>
-
- /**
- * main -さまざまな要素の印刷場所
- *
- *戻り値: 何かが失敗した場合は EXIT_FAILURE を返します。それ以外の場合はEXIT_SUCCESS
- */
- intメイン(void)
- {
- 整数a;
- void *p;
-
- printf( "aのアドレス: %p\n" , (void *)&a);
- __p = malloc(98);
- (p == NULL )の場合
- {
- fprintf(stderr, "mallocできません\n" );
- 戻り値(EXIT_FAILURE);
- }
- printf( "ヒープ内に割り当てられたスペース: %p\n" , p);
- printf( "関数mainのアドレス: %p\n" , (void *)main);
- 戻り値(EXIT_SUCCESS);
- }
- コンパイルして実行します: gcc main.c -o test; 。/テスト
- 出力:
- アドレス: 0x7ffed846de2c
- 割り当てられたスペース ヒープ内: 0x561b9ee8c670
- 住所 関数メイン: 0x561b9deb378a
main(0x561b9deb378a) < heap(0x561b9ee8c670) < (0x7ffed846de2c) なので、分布図は次のように描くことができます。 仮想メモリスタックヒープ実行可能ファイル.png コマンドライン引数と環境変数 プログラムエントリのメイン関数はパラメータを渡すことができます: - 最初のパラメータ(argc):コマンドラインパラメータの数
- 2番目のパラメータ(argv):コマンドラインパラメータの配列へのポインタ
- 3番目のパラメータ(env):環境変数の配列へのポインタ
プログラムは仮想メモリ内のこれらの要素の場所を確認できます。 - #include <stdlib.h>
- #include <stdio.h>
- #include <文字列.h>
-
- /**
- * main -さまざまな要素の印刷場所
- *
- *戻り値: 何かが失敗した場合は EXIT_FAILURE を返します。それ以外の場合はEXIT_SUCCESS
- */
- int main( int ac, char **av, char **env)
- {
- 整数a;
- void *p;
- 整数i;
-
- printf( "aのアドレス: %p\n" , (void *)&a);
- __p = malloc(98);
- (p == NULL )の場合
- {
- fprintf(stderr, "mallocできません\n" );
- 戻り値(EXIT_FAILURE);
- }
- printf( "ヒープ内に割り当てられたスペース: %p\n" , p);
- printf( "関数mainのアドレス: %p\n" , (void *)main);
- printf( "メイン関数の最初のバイト:\n\t" );
- (i = 0; i < 15; i++)の場合
- {
- printf( "%02x " , ((unsigned char *)main)[i]);
- }
- printf( "\n" );
- printf( "引数の配列のアドレス: %p\n" , (void *)av);
- printf( "引数のアドレス:\n\t" );
- (i = 0; i < ac; i++)の場合
- {
- printf( "[%s]:%p " , av[i], av[i]);
- }
- printf( "\n" );
- printf( "環境変数の配列のアドレス: %p\n" , (void *)env);
- printf( "最初の環境変数のアドレス: %p\n" , (void *)(env[0]));
- 戻り値(EXIT_SUCCESS);
- }
- コンパイルして実行します: gcc main.c -o test; ./test こんにちは
- 出力:
- アドレス: 0x7ffcc154a748
- 割り当てられたスペース ヒープ内: 0x559bd1bee670
- 住所 関数メイン: 0x559bd09807ca
- メイン関数の最初のバイト:
- 55 48 89 e5 48 83 ec 40 89 7d dc 48 89 75 d0
- 引数の配列のアドレス: 0x7ffcc154a848
- 引数のアドレス:
- [./test]:0x7ffcc154b94f [nihao]:0x7ffcc154b956 [hello]:0x7ffcc154b95c
- 環境変数の配列のアドレス: 0x7ffcc154a868
- 最初の環境変数のアドレス: 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 かどうかを確認し、配列のサイズを取得できます。コードは次のとおりです。 - #include <stdlib.h>
- #include <stdio.h>
- #include <文字列.h>
-
- /**
- * main -さまざまな要素の印刷場所
- *
- *戻り値: 何かが失敗した場合は EXIT_FAILURE を返します。それ以外の場合はEXIT_SUCCESS
- */
- int main( int ac, char **av, char **env)
- {
- 整数a;
- void *p;
- 整数i;
- 整数 サイズ;
-
- printf( "aのアドレス: %p\n" , (void *)&a);
- __p = malloc(98);
- (p == NULL )の場合
- {
- fprintf(stderr, "mallocできません\n" );
- 戻り値(EXIT_FAILURE);
- }
- printf( "ヒープ内に割り当てられたスペース: %p\n" , p);
- printf( "関数mainのアドレス: %p\n" , (void *)main);
- printf( "メイン関数の最初のバイト:\n\t" );
- (i = 0; i < 15; i++)の場合
- {
- printf( "%02x " , ((unsigned char *)main)[i]);
- }
- printf( "\n" );
- printf( "引数の配列のアドレス: %p\n" , (void *)av);
- printf( "引数のアドレス:\n\t" );
- (i = 0; i < ac; i++)の場合
- {
- printf( "[%s]:%p " , av[i], av[i]);
- }
- printf( "\n" );
- printf( "環境変数の配列のアドレス: %p\n" , (void *)env);
- printf( "最初の環境変数のアドレス:\n" );
- (i = 0; i < 3; i++)の場合
- {
- printf( "\t[%p]:\"%s\"\n" , env[i], env[i]);
- }
- /*サイズ env配列の*/
- 私 = 0;
- (env[i] != NULL )の間
- {
- 私は++;
- }
- 私は++; /* NULLポインタ */
- サイズ= i * sizeof( char * );
- printf( "配列envのサイズ: %d要素 -> %dバイト (0x%x)\n" , i, size , size );
- 戻り値(EXIT_SUCCESS);
- }
-
- コンパイルして実行します: gcc main.c -o test; ./test こんにちは
- 出力:
- アドレス: 0x7ffd5ebadff4
- 割り当てられたスペース ヒープ内: 0x562ba4e13670
- 住所 関数メイン: 0x562ba2f1881a
- メイン関数の最初のバイト:
- 55 48 89 e5 48 83 ec 40 89 7d dc 48 89 75 d0
- 引数の配列のアドレス: 0x7ffd5ebae0f8
- 引数のアドレス:
- [./test]:0x7ffd5ebae94f [nihao]:0x7ffd5ebae956 [hello]:0x7ffd5ebae95c
- 環境変数の配列のアドレス: 0x7ffd5ebae118
- 最初の環境変数のアドレス:
- [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:"
- [0x7ffd5ebaef4e]: "ホスト名=3e8650948c0c"
- [0x7ffd5ebaef64]: "OLDPWD=/"
- サイズ 配列env: 11 要素 -> 88 バイト (0x58)
-
- 操作結果は次のとおりです。
- ルート@3e8650948c0c:/ubuntu#bc
- 1.07.1 より
- 著作権 1991-1994、1997、1998、2000、2004、2006、2008、2012-2017 Free Software Foundation, Inc.
- これは 一切の保証のない無料ソフトウェアです。
- 詳細については、「warranty」と入力してください。
- オーベース=16
- ibase=16
- 58+7ffd5ebae118
- (standard_in) 3: 構文エラー
- 58+7FFD5EBAE118
- 7FFD5EBAE170
- やめる
- 結果は7FFD5EBAE170 != 0x7であることを示しています
結果は、7FFD5EBAE170 != 0x7ffd5ebae94f であるため、コマンド ライン パラメーターのアドレスは環境変数のアドレスに従っていないことを示しています。 これまでのチャートは次のようになります。 スタックメモリは本当に下方向に増加するのでしょうか? これは関数を呼び出すことによって確認できます。本当に下向きに成長している場合は、呼び出し関数のアドレスが呼び出される関数のアドレスよりも高くなるはずです。コードは次のとおりです。 - #include <stdlib.h>
- #include <stdio.h>
- #include <文字列.h>
-
- ボイドf(ボイド)
- {
- 整数a;
- 整数b;
- 整数c;
-
- a = 98;
- 1024 です。
- c = a * b;
- printf( "[f] a = %d, b = %d, c = a * b = %d\n" , a, b, c);
- printf( "[f] aのアドレス: %p、b = %p、c = %p\n" 、(void *)&a、(void *)&b、(void *)&c);
- }
-
- int main( int ac, char **av, char **env)
- {
- 整数a;
- void *p;
- 整数i;
- 整数 サイズ;
-
- printf( "aのアドレス: %p\n" , (void *)&a);
- __p = malloc(98);
- (p == NULL )の場合
- {
- fprintf(stderr, "mallocできません\n" );
- 戻り値(EXIT_FAILURE);
- }
- printf( "ヒープ内に割り当てられたスペース: %p\n" , p);
- printf( "関数mainのアドレス: %p\n" , (void *)main);
- 関数f();
- 戻り値(EXIT_SUCCESS);
- }
- コンパイルして実行します: gcc main.c -o test; 。/テスト
- 出力:
- アドレス: 0x7ffefc75083c
- 割り当てられたスペース ヒープ内: 0x564d46318670
- 住所 関数メイン: 0x564d45b9880e
- [f] a = 98、b = 1024、c = a * b = 100352
- [f] aのアドレス: 0x7ffefc7507ec、b = 0x7ffefc7507f0、c = 0x7ffefc7507f4
結果は次のようになります: f{a} 0x7ffefc7507ec < main{a} 0x7ffefc75083c 次の図を描くことができます。 実際、/proc ファイル システムのマップ コンテンツを表示することで、メモリの分布を表示する簡単なコードを記述することもできます。ここでは例を挙げません。 ヒープメモリ (malloc) メモリ割り当て malloc は、メモリを動的に割り当てるためによく使用される関数です。 malloc によって要求されたメモリはヒープ内に割り当てられます。 malloc はシステムコールではなく、glibc 関数であることに注意してください。 男のmalloc: - [...]動的メモリを割り当てる[...]
- void *malloc(size_tサイズ);
- [...]
- malloc()関数はサイズバイトを割り当て、 割り当てられたメモリへのポインタを返します。
malloc を呼び出さなければ、ヒープ領域はなくなります。 mallocを呼び出さないコードを見てみましょう - #include <stdlib.h>
- #include <stdio.h>
-
- /**
- * メイン - 何もしない
- *
- *戻り値: 何かが失敗した場合は EXIT_FAILURE を返します。それ以外の場合はEXIT_SUCCESS
- */
- intメイン(void)
- {
- char を取得する
- 戻り値(EXIT_SUCCESS);
- }
- コンパイルして実行します: gcc test.c -o 2; ./2
- ステップ 1: ps aux | grep \ \./2$
- 出力:
- zjucad 3023 0.0 0.0 4352 788 ポイント/3 S+ 13:58 0:00 ./2
- ステップ2: /proc/3023/maps
- 出力:
- 00400000-00401000 r-xp 00000000 08:01 811723 /home/zjucad/wangzhiqiang/2
- 00600000-00601000 r
- 00601000-00602000 rw-p 00001000 08:01 811723 /home/zjucad/wangzhiqiang/2
- 007a4000-007c5000 rw-p 00000000 00:00 0 [ヒープ]
- 7f954ca02000-7f954cbc2000 r-xp 00000000 08:01 8661324 /lib/x86_64-linux-gnu/libc-2.23.so
- 7f954cbc2000-7f954cdc2000
- 7f954cdc2000-7f954cdc6000 r
- 7f954cdc6000-7f954cdc8000 rw-p 001c4000 08:01 8661324 /lib/x86_64-linux-gnu/libc-2.23.so
- 7f954cdc8000-7f954cdcc000 rw-p 00000000 00:00 0
- 7f954cdcc000-7f954cdf2000 r-xp 00000000 08:01 8661310 /lib/x86_64-linux-gnu/ld-2.23.so
- 7f954cfd2000-7f954cfd5000 rw-p 00000000 00:00 0
- 7f954cff1000-7f954cff2000 r
- 7f954cff2000-7f954cff3000 rw-p 00026000 08:01 8661310 /lib/x86_64-linux-gnu/ld-2.23.so
- 7f954cff3000-7f954cff4000 rw-p 00000000 00:00 0
- 7ffed68a1000-7ffed68c2000 rw-p 00000000 00:00 0 [スタック]
- 7ffed690e000-7ffed6911000 r
- 7ffed6911000-7ffed6913000 r-xp 00000000 00:00 0 [vdso]
- ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
ご覧のとおり、mallocが呼び出されない場合、マップには[ヒープ]がありません。 malloc を使ってプログラムを実行してみましょう。 - #include <stdio.h>
- #include <stdlib.h>
-
- /**
- * main - malloc が返したアドレスを出力します
- *
- *戻り値: 何かが失敗した場合は EXIT_FAILURE を返します。それ以外の場合はEXIT_SUCCESS
- */
- intメイン(void)
- {
- void *p;
-
- 1. 呼び出し側で malloc() を呼び出す。
- printf( "%p\n" , p);
- char を取得する
- 戻り値(EXIT_SUCCESS);
- }
- コンパイルして実行: gcc test.c -o 3; ./3
- 出力: 0xcc7010
- 検証手順と出力:
- zjucad@zjucad-ONDA-H110-MINI-V3-01:~/wangzhiqiang$ ps aux | grep \ \./3$
- zjucad 3113 0.0 0.0 4352 644 ポイント/3 S+ 14:06 0:00 ./3
- zjucad@zjucad-ONDA-H110-MINI-V3-01:~/wangzhiqiang$ cat /proc/3113/maps
- 00400000-00401000 r-xp 00000000 08:01 811726 /home/zjucad/wangzhiqiang/3
- 00600000-00601000 r
- 00601000-00602000 rw-p 00001000 08:01 811726 /home/zjucad/wangzhiqiang/3
- 00cc7000-00ce8000 rw-p 00000000 00:00 0 [ヒープ]
- 7fc7e9128000-7fc7e92e8000 r-xp 00000000 08:01 8661324 /lib/x86_64-linux-gnu/libc-2.23.so
- 7fc7e92e8000-7fc7e94e8000
- 7fc7e94e8000-7fc7e94ec000 r
- 7fc7e94ec000-7fc7e94ee000 rw-p 001c4000 08:01 8661324 /lib/x86_64-linux-gnu/libc-2.23.so
- 7fc7e94ee000-7fc7e94f2000 rw-p 00000000 00:00 0
- 7fc7e94f2000-7fc7e9518000 r-xp 00000000 08:01 8661310 /lib/x86_64-linux-gnu/ld-2.23.so
- 7fc7e96f8000-7fc7e96fb000 rw-p 00000000 00:00 0
- 7fc7e9717000-7fc7e9718000 r
- 7fc7e9718000-7fc7e9719000 rw-p 00026000 08:01 8661310 /lib/x86_64-linux-gnu/ld-2.23.so
- 7fc7e9719000-7fc7e971a000 rw-p 00000000 00:00 0
- 7ffc91c18000-7ffc91c39000 rw-p 00000000 00:00 0 [スタック]
- 7ffc91d5f000-7ffc91d62000 r
- 7ffc91d62000-7ffc91d64000 r-xp 00000000 00:00 0 [vdso]
- ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]
プログラムに malloc が含まれている場合、マップ内に [heap] セグメントがあり、malloc によって返されるアドレスはヒープ内のアドレス セグメント内にありますが、返されるアドレスはヒープ内の先頭ではなくなり、0x10 バイトの差が生じます。なぜ?以下を参照してください: strace、brk、sbrk malloc はシステムコールではなく、通常の関数です。ヒープメモリを操作するには、特定のシステムコールを呼び出す必要があります。プロセスのシステムコールとシグナルは、strace ツールを使用して追跡できます。システム コールが malloc によって生成されたことを確認するために、問題の特定を容易にするために、 malloc の前後に write システム コールが追加されます。 - #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
-
- /**
- * main -どのシステムコール mallocが使用されているかを調べます
- *
- *戻り値: 何かが失敗した場合は EXIT_FAILURE を返します。それ以外の場合はEXIT_SUCCESS
- */
- intメイン(void)
- {
- void *p;
-
- write(1, "BEFORE MALLOC\n" , 14);
- 1. 呼び出し側で malloc() を呼び出す。
- write(1, "AFTER MALLOC\n" , 13);
- printf( "%p\n" , p);
- char を取得する
- 戻り値(EXIT_SUCCESS);
- }
- コンパイルして実行: gcc test.c -o 4
- zjucad@zjucad-ONDA-H110-MINI-V3-01:~/wangzhiqiang$ strace ./4
- execve( "./4" , [ "./4" ], [/* 34 個の変数 */]) = 0
- brk( NULL ) = 0x781000
- access( "/etc/ld.so.nohwcap" , F_OK) = -1 ENOENT (そのようなファイルまたはディレクトリはありません)
- access( "/etc/ld.so.preload" , R_OK) = -1 ENOENT (そのようなファイルまたはディレクトリはありません)
- オープン( "/etc/ld.so.cache" 、 O_RDONLY|O_CLOEXEC) = 3
- fstat(3, {st_mode=S_IFREG|0644, st_size=111450, ...}) = 0
- mmap( NULL , 111450, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f37720fa000
- 閉じる(3)= 0
- access( "/etc/ld.so.nohwcap" , F_OK) = -1 ENOENT (そのようなファイルまたはディレクトリはありません)
- オープン( "/lib/x86_64-linux-gnu/libc.so.6" 、 O_RDONLY|O_CLOEXEC) = 3
- 読み取り(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
- fstat(3, {st_mode=S_IFREG|0755, st_size=1868984, ...}) = 0
- mmap( NULL , 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f37720f9000
- mmap( NULL 、 3971488 、 PROT_READ|PROT_EXEC 、 MAP_PRIVATE|MAP_DENYWRITE 、 3 、 0) = 0x7f3771b27000
- mprotect(0x7f3771ce7000, 2097152, PROT_NONE) = 0
- mmap(0x7f3771ee7000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c0000) = 0x7f3771ee7000
- mmap(0x7f3771eed000, 14752, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f3771eed000
- 閉じる(3)= 0
- mmap( NULL , 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f37720f8000
- mmap( NULL , 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f37720f7000
- arch_prctl(ARCH_SET_FS, 0x7f37720f8700) = 0
- mprotect(0x7f3771ee7000, 16384, PROT_READ) = 0
- mprotect(0x600000, 4096, PROT_READ) = 0
- mprotect(0x7f3772116000, 4096, PROT_READ) = 0
- マンマップ(0x7f37720fa000, 111450) = 0
- write(1, "BEFORE MALLOC\n" , 14BEFORE MALLOC
- ) = 14
- brk( NULL ) = 0x781000
- brk(0x7a2000) = 0x7a2000
- write(1, "MALLOC後\n" , 13MALLOC後
- ) = 13
- fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 3), ...}) = 0
- 書き込み(1, "0x781010\n" , 90x781010
- ) = 9
- fstat(0, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 3), ...}) = 0
最後の数行の出力から、 malloc は主に brk システム コールを呼び出してヒープ メモリを操作していることがわかります。 - 男brk
- ...
- int brk(void *addr);
- void *sbrk(intptr_t 増分);
- ...
- 説明
- brk()とsbrk()はプログラムブレークの位置を変更します。
- 終わり プロセスのデータセグメントの(つまり、プログラムブレークは最初の
- 終了後の場所 初期化されていないデータ セグメントの)。増加
- プログラムブレークはプロセスにメモリを割り当てる効果があります。減少する‐
- ブレークを実行するとメモリが確保されます。
-
- brk()は終了を設定します データセグメントの値をaddrで指定された値に
- その値は合理的で、システムには十分なメモリがあり、プロセス
- 最大データサイズを超えません(Setrlimit(2)を参照)。
-
- SBRK()は、プログラムのデータ空間を増加させます 増分バイトで。通話
- 0の増分のSBRK()を使用して、現在の位置を見つけることができます
- プログラムが壊れます。
プログラム割り込みは、仮想メモリのプログラムデータセグメントの終了後の最初の場所のアドレスです。 MallocはBRKまたはSBRKを呼び出し、プログラム割り込みの値を増やして、メモリを動的に割り当てる新しいスペースを作成します。 BRKへの最初の呼び出しは、現在のプログラム割り込みのアドレスを返し、BRKへの2回目の呼び出しもプログラム割り込みのアドレスを返します。 2番目のBRKの返品アドレスは、プログラム割り込みアドレスを増やすことにより、最初のBRK.BRKの返品アドレスよりも大きいことがわかります。ヒープアドレス範囲が表示されることがわかります。また、CAT/Proc/[PID]/マップを介して検証することもできます。実際の検証結果はここには投稿されません。 複数のマロック mallocが複数回である場合はどうなりますか?コードは次のとおりです。 - #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
-
- /**
- *メイン -マロックへの多くの呼び出し
- *
- *返品:何かが失敗した場合はexit_failure。それ以外の場合はexit_success
- */
- int main(void)
- {
- void *p;
-
- 書き込み(1、 "malloc#0 \ n" 、17);
- p = malloc(1024);
- write(1、 "malloc#0 \ n" 、16);
- printf( "%p \ n" 、p);
-
- 書き込み(1、 "malloc#1 \ nの前" 、17);
- p = malloc(1024);
- 書き込み(1、 "後のmalloc#1 \ n" 、16);
- printf( "%p \ n" 、p);
-
- 書き込み(1、 "malloc#2 \ nの前" 、17);
- p = malloc(1024);
- 書き込み(1、 "malloc#2 \ nの後" 、16);
- printf( "%p \ n" 、p);
-
- 書き込み(1、 "malloc#3 \ nの前" 、17);
- p = malloc(1024);
- 書き込み(1、 "malloc#3 \ nの後" 、16);
- printf( "%p \ n" 、p);
-
- getchar();
- return (exit_success);
- }
- コンパイルと実行:gcc test.c -o 5; Strace ./5
- 概要出力の結果は次のとおりです。
- 書き込み(1、 "malloc#0 \ nの前" 、17before malloc#0
- )= 17
- BRK( null )= 0x561605c7a000
- brk(0x561605c9b000)= 0x561605c9b000
- 書き込み(1、 "後のmalloc#0 \ n" 、16after malloc#0
- )= 16
- fstat(1、{st_mode = s_ifchr | 0620、st_rdev = makedev(136、0)、...})= 0
- 書き込み(1、 "0x561605c7a260 \ n" 、150x561605c7a260
- )= 15
- 書き込み(1、 "malloc#1 \ nの前" 、17before malloc#1
- )= 17
- 書き込み(1、 "後のmalloc#1 \ n" 、16after malloc#1
- )= 16
- 書き込み(1、 "0x561605c7aa80 \ n" 、150x561605c7aa80
- )= 15
- 書き込み(1、 "malloc#2 \ nの前" 、17before malloc#2
- )= 17
- 書き込み(1、 "後のmalloc#2 \ n" 、16after malloc#2
- )= 16
- 書き込み(1、 "0x561605c7ae90 \ n" 、150x561605c7ae90
- )= 15
- 書き込み(1、 "malloc#3 \ nの前" 、17before malloc#3
- )= 17
- 書き込み(1、 "後のmalloc#3 \ n" 、16after malloc#3
- )= 16
- 書き込み(1、 "0x561605c7b2a0 \ n" 、150x561605c7b2a0
- )= 15
- fstat(0、{st_mode = s_ifchr | 0620、st_rdev = makedev(136、0)、...})= 0
Mallocが呼び出されるたびに、BRKシステムコールがトリガーされるわけではないことがわかります。 Mallocが初めて呼び出されると、プログラム割り込みアドレスがBRKシステムコールを介して変更され、大きなメモリスペースが割り当てられます。マロックは後で呼び出されます。以前に割り当てられたメモリスペースが最初に使用されます。 BRKシステムコールは、内部メモリスペースが再び外側に割り当てるのに十分ではないまで、再びトリガーされません。 0x10欠落している16バイトは何ですか 上記の分析は、プログラムのMallocへの最初の呼び出しによって返されたアドレスが、ヒープセグメントの最初のアドレスではなく、0x10バイトであることを示しています。では、これらの16バイトは正確には何ですか?最初の16バイトのコンテンツは、プログラムを通じて印刷できます。 - コンパイルと実行:gcc test.c -o test; ./ test
- 出力:
- 0x5589436ce260
- 0x5589436ce250のバイト:
- 00 00 00 00 00 00 00 00 00 11 04 00 00 00 00 00
- 0x5589436CEA80
- 0x5589436CEA70のバイト:
- 00 00 00 00 00 00 00 00 00 00 11 08 00 00 00 00 00
- 0x5589436CF290
- 0x5589436CF280のバイト:
- 00 00 00 00 00 00 00 00 00 11 0C 00 00 00 00 00 00
- 0x5589436cfea0
- 0x5589436cfe90のバイト:
- 00 00 00 00 00 00 00 00 00 11 10 00 00 00 00 00 00
- 0x5589436D0EB0
- 0x5589436d0ea0のバイト:
- 00 00 00 00 00 00 00 00 00 11 14 00 00 00 00 00 00
- 0x5589436D22C0
- 0x5589436d22b0のバイト:
- 00 00 00 00 00 00 00 00 00 00 11 18 00 00 00 00 00
- 0x5589436d3ad0
- 0x5589436d3ac0のバイト:
- 00 00 00 00 00 00 00 00 00 11 1C 00 00 00 00 00
- 0x5589436D56E0
- 0x5589436d56d0のバイト:
- 00 00 00 00 00 00 00 00 00 11 20 00 00 00 00 00 00
- 0x5589436D76F0
- 0x5589436d76e0のバイト:
- 00 00 00 00 00 00 00 00 00 11 24 00 00 00 00 00 00
- 0x5589436d9b00
- 0x5589436D9AF0のバイト:
- 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バイトを操作して、上記のルールが正しいかどうかを確認することもできます。コードは自由に呼び出されてメモリをリリースしないことに注意してください。 - #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
-
- /**
- * PMEM -MEMを印刷します
- * @P:印刷を開始するメモリアドレス
- * @bytes:印刷するバイト数
- *
- *返品:何もありません
- */
- void pmem(void *p、unsigned int bytes)
- {
- 符号なしのchar *ptr;
- 署名されていないint i;
-
- ptr =(unsigned char *)p;
- for (i = 0; i <bytes; i ++)
- {
- if(i!= 0)
- {
- printf( "" );
- }
- printf( "%02x" 、 *(ptr + i));
- }
- printf( "\ n" );
- }
-
- /**
- *メイン - ソースコードを確認します
- *
- *返品:何かが失敗した場合はexit_failure。それ以外の場合はexit_success
- */
- int main(void)
- {
- void *p;
- int i;
- size_t size_of_the_chunk;
- size_t size_of_the_previous_chunk;
- void *チャンク[10];
-
- for (i = 0; i <10; i ++)
- {
- p = malloc(1024 *(i + 1));
- チャンク[i] =(void *)(( char *)p -0x10);
- printf( "%p \ n" 、p);
- }
- free (( char *)(chunks [3]) + 0x10);
- free (( char *)(chunks [7]) + 0x10);
- for (i = 0; i <10; i ++)
- {
- p =チャンク[i];
- printf( "chunks [%d]:" 、i);
- PMEM(P、0x10);
- size_of_the_chunk = *((size_t *)(( char *)p + 8))-1;
- size_of_the_previous_chunk = *((size_t *)(( char *)p));
- printf( "チャンク[%d]:%p、size =%li、prev =%li \ n" 、
- i、p、size_of_the_chunk、size_of_the_previous_chunk);
- }
- return (exit_success);
- }
- コンパイルして出力を実行します:
- root@3e8650948c0c:/ubuntu#gcc test.c -oテスト
- root@3e8650948c0c:/ubuntu#./test
- 0x559721DE4260
- 0x559721DE4A80
- 0x559721DE5290
- 0x559721DE5EA0
- 0x559721DE6EB0
- 0x559721DE82C0
- 0x559721DE9AD0
- 0x559721DEB6E0
- 0x559721ded6f0
- 0x559721DEFB00
- チャンク[0]:00 00 00 00 00 00 00 00 11 04 00 00 00 00 00 00
- チャンク[0]:0x559721DE4250、 size = 1040、prev = 0
- チャンク[1]:00 00 00 00 00 00 00 00 11 08 00 00 00 00 00 00
- チャンク[1]:0x559721de4a70、 size = 2064、prev = 0
- チャンク[2]:00 00 00 00 00 00 00 00 11 0C 00 00 00 00 00 00
- チャンク[2]:0x559721DE5280、 size = 3088、prev = 0
- チャンク[3]:00 00 00 00 00 00 00 00 11 10 00 00 00 00 00 00 00
- チャンク[3]:0x559721DE5E90、 size = 4112、prev = 0
- チャンク[4]:10 10 00 00 00 00 00 00 00 10 14 00 00 00 00 00 00
- チャンク[4]:0x559721DE6EA0、 size = 5135、prev = 4112
- チャンク[5]:00 00 00 00 00 00 00 00 11 18 00 00 00 00 00 00
- チャンク[5]:0x559721de82b0、 size = 6160、prev = 0
- チャンク[6]:00 00 00 00 00 00 00 00 11 1C 00 00 00 00 00 00
- チャンク[6]:0x559721de9ac0、 size = 7184、prev = 0
- チャンク[7]:00 00 00 00 00 00 00 00 11 20 00 00 00 00 00 00 00
- チャンク[7]:0x559721deb6d0、 size = 8208、prev = 0
- チャンク[8]:10 20 00 00 00 00 00 00 00 10 24 00 00 00 00 00 00
- チャンク[8]:0x559721ded6e0、 size = 9231、prev = 8208
- チャンク[9]:00 00 00 00 00 00 00 00 11 28 00 00 00 00 00 00
- チャンク[9]:0x559721DEFAF0、 size = 10256、prev = 0
その結果、図に示すように、Mallocによって返されるアドレスの前の16バイトが割り当てられたメモリサイズを表すことができることがわかります。 上記は、自由なメモリへの呼びかけの結果であることに注意してください。ただし、Mallocは8バイトのみを使用して、割り当てられたメモリサイズを表します。それでは、他の8バイトはどういう意味ですか? malloc関数のコメントを見てください: - 1055 /*
- 1056 malloc_chunk詳細:
- 1057
- 1058(以下には、Colin Plumbによる軽く編集された説明が含まれています。)
- 1059
- 1060のメモリのチャンクは、「境界タグ」メソッドを使用して維持されます
- たとえば、KnuthまたはStandishに記載されている1061。 (ポールの論文を参照してください
- 1062ウィルソンftp://ftp.cs.utexas.edu/pub/garbage/allocsrv.ps for
- 1063そのような手法の調査。)サイズ 無料のチャンクは両方を保存します
- 各チャンクの前面に1064 最後に。これにより
- 1065断片化されたチャンクを非常に速く大きなチャンクに統合します。の
- 1066サイズフィールドは、チャンクが無料かどうかを表すビットも保持します または
- 使用中の1067 。
- 1068
- 1069割り当てられたチャンクは次のようになります:
- 1070
- 1071
- 1072 chunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- 1073 |サイズ 以前のチャンクの、unallocated(p clear)|
- 1074 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- 1075 |サイズ チャンク、バイト| a | m | p |
- 1076 mem-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- 1077 |ユーザーデータはここから始まります...。
- 1078。
- 1079。(malloc_usable_size()bytes)。
- 1080。 |
- 1081 nextchunk-> +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- 1082 | (サイズ チャンクですが、アプリケーションデータに使用されます)|
- 1083 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- 1084 |サイズ の 次のチャンク、バイト| a | 0 | 1 |
- 1085 +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
- 1086
- 1087ここで "かたまり" ほとんどの目的のためにチャンクの前面です
- 1088マロックコード、しかし「メム」 に返されるポインターです
- 1089ユーザー。 「nextchunk」 次の連続チャンクの始まりです。
これらの16バイトには2つの意味があることがわかります。最初の8バイトは、割り当てられていない以前のスペースにあるバイトの数を示します。最後の8バイトは、mallocによって割り当てられたバイトサイズを示します。無料のコードを介してそれをチェックしてください: - #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
-
- /**
- * PMEM -MEMを印刷します
- * @P:印刷を開始するメモリアドレス
- * @bytes:印刷するバイト数
- *
- *返品:何もありません
- */
- void pmem(void *p、unsigned int bytes)
- {
- 符号なしのchar *ptr;
- 署名されていないint i;
-
- ptr =(unsigned char *)p;
- for (i = 0; i <bytes; i ++)
- {
- if(i!= 0)
- {
- printf( "" );
- }
- printf( "%02x" 、 *(ptr + i));
- }
- printf( "\ n" );
- }
-
- /**
- *メイン - ソースコードを確認します
- *
- *返品:何かが失敗した場合はexit_failure。それ以外の場合はexit_success
- */
- int main(void)
- {
- void *p;
- int i;
- size_t size_of_the_chunk;
- size_t size_of_the_previous_chunk;
- void *チャンク[10];
-
- for (i = 0; i <10; i ++)
- {
- p = malloc(1024 *(i + 1));
- チャンク[i] =(void *)(( char *)p -0x10);
- printf( "%p \ n" 、p);
- }
- free (( char *)(chunks [3]) + 0x10);
- free (( char *)(chunks [7]) + 0x10);
- for (i = 0; i <10; i ++)
- {
- p =チャンク[i];
- printf( "chunks [%d]:" 、i);
- PMEM(P、0x10);
- size_of_the_chunk = *((size_t *)(( char *)p + 8))-1;
- size_of_the_previous_chunk = *((size_t *)(( char *)p));
- printf( "チャンク[%d]:%p、size =%li、prev =%li \ n" 、
- i、p、size_of_the_chunk、size_of_the_previous_chunk);
- }
- return (exit_success);
- }
-
- コンパイルして出力を実行します:
- root@3e8650948c0c:/ubuntu#gcc test.c -oテスト
- root@3e8650948c0c:/ubuntu#./test
- 0x55FBEBF20260
- 0x55FBEBF20A80
- 0x55FBEBF21290
- 0x55FBEBF21EA0
- 0x55FBEBF22EB0
- 0x55FBEBF242C0
- 0x55FBEBF25AD0
- 0x55FBEBF276E0
- 0x55FBEBF296F0
- 0x55FBEBF2BB00
- チャンク[0]:00 00 00 00 00 00 00 00 11 04 00 00 00 00 00 00
- チャンク[0]:0x55FBEBF20250、 size = 1040、prev = 0
- チャンク[1]:00 00 00 00 00 00 00 00 11 08 00 00 00 00 00 00
- チャンク[1]:0x55FBEBF20A70、 size = 2064、prev = 0
- チャンク[2]:00 00 00 00 00 00 00 00 11 0C 00 00 00 00 00 00
- チャンク[2]:0x55fbebf21280、 size = 3088、prev = 0
- チャンク[3]:00 00 00 00 00 00 00 00 11 10 00 00 00 00 00 00 00
- チャンク[3]:0x55fbebf21e90、 size = 4112、prev = 0
- チャンク[4]:10 10 00 00 00 00 00 00 00 10 14 00 00 00 00 00 00
- チャンク[4]:0x55fbebf22ea0、 size = 5135、prev = 4112
- チャンク[5]:00 00 00 00 00 00 00 00 11 18 00 00 00 00 00 00
- チャンク[5]:0x55fbebf242b0、 size = 6160、prev = 0
- チャンク[6]:00 00 00 00 00 00 00 00 11 1C 00 00 00 00 00 00
- チャンク[6]:0x55fbebf25ac0、 size = 7184、prev = 0
- チャンク[7]:00 00 00 00 00 00 00 00 11 20 00 00 00 00 00 00 00
- チャンク[7]:0x55fbebf276d0、 size = 8208、prev = 0
- チャンク[8]:10 20 00 00 00 00 00 00 00 10 24 00 00 00 00 00 00
- チャンク[8]:0x55fbebf296e0、 size = 9231、prev = 8208
- チャンク[9]:00 00 00 00 00 00 00 00 11 28 00 00 00 00 00 00
- チャンク[9]:0x55fbebf2baf0、 size = 10256、prev = 0
プログラムコードでは、3と7のデータブロックのスペースが無料であるため、最初の8バイトと4の8バイトがすべて0ではありません。他のものとは異なり、以前のデータブロックが割り当てられていないサイズを示します。また、4ブロックと8ブロックの最後の8バイトを他のブロックと同様に1つ追加する必要はないことに注意することもできます。 Mallocは、以前のデータブロックが割り当てられているかどうかの兆候として1を追加するために使用されると結論付けることができます。 1を追加すると、以前のデータブロックが割り当てられていることを意味します。したがって、以前のプログラムコードは次の形式に変更できます。 - #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
-
- /**
- * PMEM -MEMを印刷します
- * @P:印刷を開始するメモリアドレス
- * @bytes:印刷するバイト数
- *
- *返品:何もありません
- */
- void pmem(void *p、unsigned int bytes)
- {
- 符号なしのchar *ptr;
- 署名されていないint i;
-
- ptr =(unsigned char *)p;
- for (i = 0; i <bytes; i ++)
- {
- if(i!= 0)
- {
- printf( "" );
- }
- printf( "%02x" 、 *(ptr + i));
- }
- printf( "\ n" );
- }
-
- /**
- *メイン -正しいチェックで更新
- *
- *返品:何かが失敗した場合はexit_failure。それ以外の場合はexit_success
- */
- int main(void)
- {
- void *p;
- int i;
- size_t size_of_the_chunk;
- size_t size_of_the_previous_chunk;
- void *チャンク[10];
- Char prev_used;
-
- for (i = 0; i <10; i ++)
- {
- p = malloc(1024 *(i + 1));
- チャンク[i] =(void *)(( char *)p -0x10);
- }
- free (( char *)(chunks [3]) + 0x10);
- free (( char *)(chunks [7]) + 0x10);
- for (i = 0; i <10; i ++)
- {
- p =チャンク[i];
- printf( "chunks [%d]:" 、i);
- PMEM(P、0x10);
- size_of_the_chunk = *((size_t *)(( char *)p + 8));
- prev_used = size_of_the_chunk&1;
- size_of_the_chunk - = prev_used;
- size_of_the_previous_chunk = *((size_t *)(( char *)p));
- printf( "チャンク[%d]:%p、size =%li、prev(%s)=%li \ n" 、
- I、P、size_of_the_chunk、
- (prev_used? "割り当て" : "unallocated" )、size_of_the_previous_chunk);
- }
- return (exit_success);
- }
- コンパイルして出力を実行します:
- root@3e8650948c0c:/ubuntu#gcc test.c -oテスト
- root@3e8650948c0c:/ubuntu#./test
- チャンク[0]:00 00 00 00 00 00 00 00 11 04 00 00 00 00 00 00
- Chunks [0]:0x56254f888250、 size = 1040、prev(割り当て)= 0
- チャンク[1]:00 00 00 00 00 00 00 00 11 08 00 00 00 00 00 00
- チャンク[1]:0x56254f888660、 size = 2064、prev(割り当て)= 0
- チャンク[2]:00 00 00 00 00 00 00 00 11 0C 00 00 00 00 00 00
- チャンク[2]:0x56254f888e70、 size = 3088、prev(forcated)= 0
- チャンク[3]:00 00 00 00 00 00 00 00 11 04 00 00 00 00 00 00
- チャンク[3]:0x56254f889a80、 size = 1040、prev(割り当て)= 0
- チャンク[4]:00 0c 00 00 00 00 00 00 10 14 00 00 00 00 00 00
- チャンク[4]:0x56254f88aa90、 size = 5136、prev(unallocated)= 3072
- チャンク[5]:00 00 00 00 00 00 00 00 11 18 00 00 00 00 00 00
- チャンク[5]:0x56254f88bea0、 size = 6160、prev(forcated)= 0
- チャンク[6]:00 00 00 00 00 00 00 00 11 1C 00 00 00 00 00 00
- チャンク[6]:0x56254f88d6b0、 size = 7184、prev(割り当て)= 0
- チャンク[7]:00 00 00 00 00 00 00 00 11 20 00 00 00 00 00 00 00
- チャンク[7]:0x56254f88f2c0、 size = 8208、prev(割り当て)= 0
- チャンク[8]:10 20 00 00 00 00 00 00 00 10 24 00 00 00 00 00 00
- チャンク[8]:0x56254f8912d0、 size = 9232、prev(unallocated)= 8208
- チャンク[9]:00 00 00 00 00 00 00 00 11 28 00 00 00 00 00 00
- チャンク[9]:0x56254f8936e0、 size = 10256、prev(割り当て)= 0
ヒープスペースは上方に成長していますか? コードで確認してください: - #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
-
- /**
- *メイン - プログラムの移動
- *
- *返品:何かが失敗した場合はexit_failure。それ以外の場合はexit_success
- */
- int main(void)
- {
- int i;
-
- 書き込み(1、 "start \ n" 、6);
- malloc(1);
- getchar();
- 書き込み(1、 "loop \ n" 、5);
- for (i = 0; i <0x25000 / 1024; i ++)
- {
- Malloc(1024);
- }
- 書き込み(1、 "end \ n" 、4);
- getchar();
- return (exit_success);
- }
- パーツサマリー出力をコンパイルして実行します。
- root@3e8650948c0c:/ubuntu#gcc test.c -oテスト
- root@3e8650948c0c:/ubuntu#strace ./test
- execve( "./Test"、["./Test"]、0x7ffe0d7cbd80/ * 10 vars */) = 0
- BRK( null )= 0x555A2428F000
- アクセス( "/etc/ld.so.nohwcap"、f_ok)= -1 enoent(そのようなファイルやディレクトリなし)
- アクセス( "/etc/ld.so.preload"、r_ok)= -1 enoent(そのようなファイルまたはディレクトリなし)
- openat(at_fdcwd、 "/etc/ld.so.cache"、o_rdonly | o_cloexec)= 3
- fstat(3、{st_mode = s_ifreg | 0644、st_size = 13722、...})= 0
- mmap( null 、13722、prot_read、map_private、3、0)= 0x7f6423455000
- 閉じる(3)= 0
- アクセス( "/etc/ld.so.nohwcap"、f_ok)= -1 enoent(そのようなファイルやディレクトリなし)
- openat(at_fdcwd、 "/lib/x86_64-linux-gnu/libc.so.6"、o_rdonly | o_cloexec)= 3
- 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 \
- fstat(3、{st_mode = s_ifreg | 0755、st_size = 2030544、...})= 0
- mmap( null 、8192、prot_read | prot_write、map_private | map_anonymous、-1、0)= 0x7f6423453000
- mmap( null 、4131552、prot_read | prot_exec、map_private | map_denywrite、3、0)= 0x7f6422e41000
- mprotect(0x7f6423028000、2097152、prot_none)= 0
- MMAP(0x7F6423228000、24576、prot_read | prot_write、map_private | map_fixed | map_denywrite、3、0x1e7000)= 0x7f6423228000
- MMAP(0x7F642322E000、15072、prot_read | prot_write、map_private | map_fixed | map_anonymous、-1、0)= 0x7f642322e000
- 閉じる(3)= 0
- arch_prctl(arch_set_fs、0x7f64234544c0)= 0
- mprotect(0x7F6423228000、16384、prot_read)= 0
- mprotect(0x555A22F5F000、4096、prot_read)= 0
- mprotect(0x7F6423459000、4096、prot_read)= 0
- Munmap(0x7F6423455000、13722)= 0
- 書き込み(1、 "start \ n" 、6start
- )= 6
- BRK( null )= 0x555A2428F000
- BRK(0x555A242B0000)= 0x555A242B0000
- fstat(0、{st_mode = s_ifchr | 0620、st_rdev = makedev(136、0)、...})= 0
- 読む(0、
- "\ n" 、1024)= 1
- 書き込み(1、 "loop \ n" 、5loop
- )= 5
- BRK(0x555A242D1000)= 0x555A242D1000
- BRK(0x555A242F2000)= 0x555A242F2000
- BRK(0x555A24313000)= 0x555A24313000
- BRK(0x555A24334000)= 0x555A24334000
- BRK(0x555A24355000)= 0x555A24355000
- BRK(0x555A24376000)= 0x555A24376000
- BRK(0x555A24397000)= 0x555A24397000
- BRK(0x555A243B8000)= 0x555A243B8000
- BRK(0x555A243D9000)= 0x555A243D9000
- BRK(0x555A243FA000)= 0x555A243FA000
ヒープスペースが上方に成長していることがわかります。 ランダム化アドレススペースレイアウト 最初から今まで、多くのプロセスが実行されています。対応するプロセスのマップを見ると、各プロセスのヒープの開始アドレスと実行可能ファイルプログラムの最終アドレスがすぐに隣接しておらず、ギャップは毎回異なることがわかりました。 - [3718]:01195000 - 00602000 = B93000
- [3834]:024D6000 - 00602000 = 1ED4000
- [4014]:00E70000 - 00602000 = 86E000
- [4172]:01314000 –00602000 = D12000
- [7972]:00901000 - 00602000 = 2FF000
この違いがランダムであることがわかります。 FS/BINFMT_ELF.Cのソースコードを確認してください - if(( current- > flags&pf_randomize)&&(randomize_va_space> 1)){
- current- > mm-> brk = current- > mm-> start_brk =
- arch_randomize_brk( current- > mm);
- #ifdef compat_brk_randomized
- 現在- > brk_randomized = 1;
- #終了
- }
- //現在- > mm-> brkは現在のプロセスプログラムの中断のアドレスです
arch_randomize_brk関数はarch/x86/kernel/process.cにあります - 符号なしの長いarch_randomize_brk(struct mm_struct *mm)
- {
- unsigned long range_end = mm-> brk + 0x02000000;
- return randomize_range(mm-> brk、range_end、0)? :mm-> brk;
- }
drivers/char/random.cのランダム化_range関数 - /*
- * RANDIOMIZE_RANGE()は、そのように
- *
- * [...... <Range> ....]
- *終了を開始します
- *
- * a <range> with サイズ リターン値から始まる「レン」は内部にあります
- * [start、 end ]で定義されている領域ですが、それ以外の場合はランダム化されます。
- */
- 符号なしの長い
- andimize_range(符号なしのロングスタート、符号なしの長い終了、符号なし長いレン)
- {
- 符号なしの長距離= end -len -start;
-
- if( end <= start + len)
- 0を返します。
- return page_align(get_random_int()%range + start);
- }
上記の違いは、実際には0-0x02000000の乱数であることがわかります。このテクノロジーはASLRと呼ばれます(アドレススペースレイアウトランダム化)。これは、ハッカーが攻撃を効果的に妨げる可能性のある仮想メモリのスタックスペースの位置をランダムに配置するコンピューターセキュリティテクノロジーです。上記の分析により、メモリ分布図は次のように描画できます。 Malloc(0)はどうなりましたか? malloc(0)が呼び出された場合、コードは次のとおりです。 - #include <stdio.h>
- #include <stdlib.h>
- #include <unistd.h>
-
- /**
- * PMEM -MEMを印刷します
- * @P:印刷を開始するメモリアドレス
- * @bytes:印刷するバイト数
- *
- *返品:何もありません
- */
- void pmem(void *p、unsigned int bytes)
- {
- 符号なしのchar *ptr;
- 署名されていないint i;
-
- ptr =(unsigned char *)p;
- for (i = 0; i <bytes; i ++)
- {
- if(i!= 0)
- {
- printf( "" );
- }
- printf( "%02x" 、 *(ptr + i));
- }
- printf( "\ n" );
- }
-
- /**
- *メイン - プログラムの移動
- *
- *返品:何かが失敗した場合はexit_failure。それ以外の場合はexit_success
- */
- int main(void)
- {
- void *p;
- size_t size_of_the_chunk;
- Char prev_used;
-
- p = malloc(0);
- printf( "%p \ n" 、p);
- PMEM(( char *)p -0x10、0x10);
- size_of_the_chunk = *((size_t *)(( char *)p -8));
- prev_used = size_of_the_chunk&1;
- size_of_the_chunk - = prev_used;
- printf( "chunk size =%li bytes \ n" 、size_of_the_chunk);
- return (exit_success);
- }
- コンピレーションとランニング出力は次のとおりです。
- root@3e8650948c0c:/ubuntu#gcc test.c -oテスト
- root@3e8650948c0c:/ubuntu#./test
- 0x564ECE64B260
- 00 00 00 00 00 00 00 00 00 21 00 00 00 00 00 00 00
- チャンクサイズ= 32バイト
Malloc(0)は、前述の16バイトヘッダーを含む32バイトを実際に使用していることがわかります。ただし、Malloc(0)が異なる結果出力を持つ場合があり、nullを返す場合があります。 - マンマロック
- nullは、サイズのmalloc ()へのコールを成功させることによって返される場合もあります ゼロの
動作環境 - サンプルコードは主に2つの環境で実行されます。
- Ubuntu 16.04
- gcc(ubuntu 7.4.0-1ubuntu1〜16.04〜ppa1)7.4.0
-
- Ubuntu 18.04 Docker
- gcc(ubuntu 7.4.0-1ubuntu1〜18.04.1)7.4.0
この記事は、この一連の記事から翻訳され、抽出され、あなた自身の理解と組み合わされました。コードは自分で練習されています。時間がある場合は、元の英語リンクを直接読むことができます。 仮想メモリをハッキング:C Strings& /Proc 仮想メモリをハック:VM図を描きます 仮想メモリをハック:Malloc、Heap、The Program Break |