質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.49%
C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

GDB

GDBはGNUソフトウェアシステムのための標準的なデバッガーです。

Q&A

解決済

2回答

3822閲覧

GDBで表示された内容の理解

kolona

総合スコア16

C

C言語は、1972年にAT&Tベル研究所の、デニス・リッチーが主体となって作成したプログラミング言語です。 B言語の後継言語として開発されたことからC言語と命名。そのため、表記法などはB言語やALGOLに近いとされています。 Cの拡張版であるC++言語とともに、現在世界中でもっとも普及されているプログラミング言語です。

GDB

GDBはGNUソフトウェアシステムのための標準的なデバッガーです。

0グッド

1クリップ

投稿2019/09/25 19:09

前提・実現したいこと

GDBを使ったデバッグで表示されたメッセージの意味を読み解きたいです。
別件のデバッグで、warning: HEAP・・・を受け、その表示されたアドレスに関して理解できない部分がありました。
問題を整理するため、ヒープメモリを意図的に破壊して同様の結果を得るテストプログラムを作成、実行しましたがやはりよくわかりません。

具体的には、下の例でいうと Heap block at 003311B8 の部分の数値が何なのか、この値からどのように情報を引き出せばいいのかご教授いただければと思います。

発生している問題・エラーメッセージ

warning: HEAP[HeapChek2.exe]: warning: Heap block at 003311B8 modified at 00331288 past requested size of c8

該当のソースコード

C

1 2#include <stdio.h> 3#include <tchar.h> 4#include <stdlib.h> 5#include <malloc.h> 6#include <locale.h> 7//意図的にヒープ破壊を起こして、デバッグ情報を得る練習 8int main(int argc, char **argv){ 9 10 int size, len, i; 11 TCHAR *p=NULL; 12 13 if( argc==0 || argv==NULL ) return 1; 14 15 setlocale( LC_ALL, "Japanese" );//ロケール(地域言語)を日本語でセット 16 len = 100; 17 size = len*sizeof(TCHAR); 18 19 p = malloc( size ); 20 _tcsncpy(p, _T("BCDE"), len ); 21 22 _tprintf( _T("%s@\n"), p ); 23 _tprintf( _T("p=%d\n"), p ); 24 25 for(i=0;i<5;i++){ 26 _tprintf( _T("&(p[%d]) =%d %x 「%c」\n"), i, &(p[i]), &(p[i]), p[i] ); 27 } 28 _tprintf( _T("&(p[99]) =%d %x\n"), &(p[99]), &(p[99]) ); 29 _tprintf( _T("&(p[100])=%d %x\n"), &(p[100]), &(p[100]) ); 30 31 //ヒープで確保した領域外破壊箇所 32 p[len] = 0; 33 34 free( p ); 35 36 _tprintf( _T("無事終了\n") ); 37 38 return 0; 39} 40

試したこと

GDBからはwarning: Heap block at 003311B8 modified at 00331288 past requested size of c8
というメッセージを受け取っています。
これは、
003311B8に確保したヒープメモリはc8(10進数で100)要素しか要求してないのに、お前はそれを超えた00331288にアクセスしておる。
という意味だと解釈しています。
ところが、この003311B8は実際にmallocから受け取ったアドレスからはかなり後方にあり、このメッセージからはmallocの返り値を推定することは難しいと思います。
イメージ説明説明](3705e0b23795fe495a59862351bb7a35.png)

逆算してみると、警告メッセージの003311B8はp[-4]に当たる場所です。
今回は意図的にp[100]=0;で破壊していますので破壊されるヒープのポインタpの位置は自明ですが、できれば警告メッセージからp[0]の位置を知りたいと思います。

そもそも、警告メッセージの「Heap block at 003311B8」の意味を勘違いしているのかもしれませんが、今のところなぜこんな後方のアドレスを教えてくるのかわかりません。

GDBに詳しいわけではないので、割と当たり前のことも知らないのかもしれません。解決に役立ちそうなGDBコマンドや本来のアドレスの推測法など、情報がありましたらご指導いただければと思います。

補足情報(FW/ツールのバージョンなど)

Win7 32bit
CPUはIntel Core(TM)2 Duo CPU P8700 2.53GHz、多分リトルエンディアンです。
Mingw gcc version 8.2.0 (MinGW.org GCC-8.2.0-3)
GNU gdb (GDB) 7.6.1

コンパイルコマンドは
gcc -std=gnu99 -DUNICODE -D_UNICODE -Ddebug -g3 -O0 -static -Wextra -Wshadow -W
strict-prototypes -Wmissing-declarations -Winit-self -Wswitch-default -Wswitch-e
num -Wfloat-equal -Wcast-align -Wsign-compare -ftrapv -Wall -Wuninitialized -s -
c -o HeapChek2.obj HeapChek2.c
gcc -std=gnu99 -g3 -static -ftrapv -o HeapChek2.exe HeapChek2.obj
でコンパイルとリンクをしています。

GDBでは以下の出力を得ております。

(gdb) run
Starting program: HeapChek2.exe
[New Thread 5564.0xd18]
BCDE@
p=3346880
&(p[0]) =3346880 3311c0 「B」
&(p[1]) =3346882 3311c2 「C」
&(p[2]) =3346884 3311c4 「D」
&(p[3]) =3346886 3311c6 「E」
&(p[4]) =3346888 3311c8 「」
&(p[99]) =3347078 331286
&(p[100])=3347080 331288
warning: HEAP[HeapChek2.exe]:
warning: Heap block at 003311B8 modified at 00331288 past requested size of c8

Program received signal SIGTRAP, Trace/breakpoint trap.
0x77525a1d in ntdll!RtlpNtMakeTemporaryKey ()
from C:\Windows\system32\ntdll.dll
(gdb) x 0x3311c0
0x3311c0: 0x00430042
(gdb) x 0x3311c2
0x3311c2: 0x00440043
(gdb) x 0x3311c4
0x3311c4: 0x00450044
(gdb) x 0x3311be
0x3311be: 0x00421800

どうかよろしくお願いいたします。

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

プログラムが停止した段階でbtと入力してバックトレースを表示してみてください。
SIGTRAPが発生したときに呼び出していた関数はfree関数になっているはずです。このことからfree対象のポインタに関してなにか問題があったと推測はできます。

推測ばかりで申し訳ないですが
mallocは要求されたメモリサイズより大きめにメモリ確保をして、前後にマジックワードを埋め込んでfree時にそのマジックワードが変更されていないかチェックしていると思われます。そのマジックワードが変化していたらメモリ破壊として例外を発生させていると考えられます。
そしてgdbに表示されたポインタは大きめに確保したときのポインタと思われます。

投稿2019/09/25 22:02

nomuken

総合スコア1627

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

kolona

2019/09/26 11:37

ありがとうございます。ご指摘を受けて、問題の領域の前後の領域を表示してみたところ、問題の核が見えてきました。 結論としては、gdbの吐くヒープのアドレスはヒープ管理領域のアドレスで、mallocの返り値よりも何バイトか少ない値であり、どれくらい少ないかはmalloc.cにあるヒープヘッダ(ヒープチャンク)とメモリアドレス変換マクロを見ないとわからないということになりました。 逆にいうと、私の環境では8バイトを足せばmallocの返り値に変換できるということなので、解決です! 回答の方に、同様の問題に当たった人用に調査の足跡をまとめておきます。 ありがとうございました。
guest

0

以下、同様の問題に当たった人用に調査の足跡をまとめておきます。
尚、mingwの32bit版は周辺ライブラリの対応が遅いためこういう泥臭い方法でヒープ破壊を調査していますが、最新のgccが使える環境では-fstack-protectorやMudflap、-D_FORTIFY_SOURCEなどを使うと、各種バッファオーバーフローを効率的にデバッグできると思われます。

まず、ご指摘のとおり、ヒープの末端、100要素確保した場合は101要素目から8バイト、全く同じバイト列(0xAB)が書き込まれており、これがチェック対象になっていることを確認しました。
他方、ヒープの開始地点より後ろには8バイトの不定値(実行のたびに変化する)があり、さらに後ろに8バイトの値0領域、さらに後ろに12バイトの同じ2バイト列(0xFEEE)が書き込まれており、これがチェック対象のようでした。ただし、こちらは実行時に変化します。

そこでヒープメモリの管理について改めて調べてみたところ、ヒープメモリは実装がいくつかあり、メモリ上に管理領域をリスト形式で構築するのが一般的とわかりました。
これ以上はソースを見るしかないと思い、http://ftp.gnu.org/gnu/glibc/
からglibcをダウンロードし、ソースを直に当たることにしました。

\glibc-2.30\glibc-2.30\malloc\malloc.c

C

1 2/* conversion from malloc headers to user pointers, and back */ 3 4#define chunk2mem(p) ((void*)((char*)(p) + 2*SIZE_SZ)) 5#define mem2chunk(mem) ((mchunkptr)((char*)(mem) - 2*SIZE_SZ)) 6 7struct malloc_chunk { 8 9 INTERNAL_SIZE_T mchunk_prev_size; /* Size of previous chunk (if free). */ 10 INTERNAL_SIZE_T mchunk_size; /* Size in bytes, including overhead. */ 11 12 struct malloc_chunk* fd; /* double links -- used only if free. */ 13 struct malloc_chunk* bk; 14 15 /* Only used for large blocks: pointer to next larger size. */ 16 struct malloc_chunk* fd_nextsize; /* double links -- used only if free. */ 17 struct malloc_chunk* bk_nextsize; 18};

があり、これがmallocヒープメモリ管理構造体の定義のようです。
mem2chunk(mem) の定義から、malloc()返り値を管理構造体のアドレスに変換するには
2*SIZE_SZ バイトを減少させる、という計算式のようです。
SIZE_SZは、malloc-internal.hによれば通常 size_t の別名になっているようなので、
32bit の環境では2×4=8バイト、今回のwarningで表示されたアドレスも後方に8バイトずれているので計算が合います!(サンプルプログラムのpはunicodeを格納するwcharで2バイトのサイズ、これが4要素分後方のp[-4]。)

\glibc-2.30\glibc-2.30\malloc\malloc-internal.h

C

1#ifndef INTERNAL_SIZE_T 2# define INTERNAL_SIZE_T size_t 3#endif 4/* The corresponding word size. */ 5#define SIZE_SZ (sizeof (INTERNAL_SIZE_T))

32bit環境での、ヒープメモリのメモリイメージ
イメージ説明

というわけで、GDBの出力から2*SIZE_SZを引けばmallocの値に変換できることがわかりました。
ありがとうございました。

投稿2019/09/26 11:42

kolona

総合スコア16

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

nomuken

2019/09/26 12:09

glibcを使っている場合はご説明の通りだと思います。 ただ、今回は環境がWindows+MinGWだったので純粋なglibcが使われているかどうかわからなかったのでこれに触れない回答をしてました。 https://codeday.me/jp/qa/20190303/355932.html にあるようにMinGWのmallocはWindows固有のメモリ管理APIのラッパ実装である可能性があるためです。
kolona

2019/09/26 16:03

なるほど・・・ となると、結局この問題は環境依存の部分が常にあって、汎用性のある方法はないということですね。 ありがとうございます。 デバッグコンパイルしたときのメモリ破壊の様子が違う感じがするので、もしかしたらコンパイルオプションでリンクされるmalloc()の仕様が切り替えられているのかもしれません。 しかし、それをハックするスキルはありません・・・ あと、書き忘れましたが、「bt」を使った方法も試してみました。 仰るとおり関数の履歴がでてきて、今回の試験用プログラムはデバッグできそうだったのですが、最初に引っかかったもう少し複雑なプログラムでは #0 0x77cf5a1d in ntdll!RtlpNtMakeTemporaryKey () from C:\Windows\system32\ntdll.dll #1 0x77cddd39 in ntdll!RtlImageRvaToVa () from C:\Windows\system32\ntdll.dll #2 0x77cc17c3 in ntdll!EtwSetMark () from C:\Windows\system32\ntdll.dll #3 0x77cf6a77 in ntdll!RtlpNtMakeTemporaryKey () from C:\Windows\system32\ntdll.dll #4 0x77cb9e37 in ntdll!EtwSetMark () from C:\Windows\system32\ntdll.dll #5 0x77c86396 in wcsnicmp () from C:\Windows\system32\ntdll.dll #6 0x75ce98cd in msvcrt!free () from C:\Windows\system32\msvcrt.dll ・・・ のように大量に表示されて、ヒープ破壊の関数を特定できませんでした。 なにか、僕自身が派手にやらかしているのだと思います。 ともあれ、いただいたヒントのおかげでmallocの返り値から破壊されたヒープ領域を絞り込めそうなので僕自身といたしましては大戦果であります。 ありがとうございました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.49%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問