◀返回
使用 GDB 除錯原生可執行檔
要使用哪個 GDB?
- 請使用 GDB 10.2 或更新版本。偵錯資訊已透過
mx debuginfotest
針對 10.2 進行測試。 - 請注意,較新版本可能會稍微不同的除錯器輸出格式(例如,可能導致 CI/CD 閘道檢查失敗)
- 最近 Linux 版本中綑綁的 GDB 可正常用於除錯工作階段
建置具有除錯資訊的原生可執行檔
要建置具有除錯資訊的原生可執行檔,在編譯應用程式時,請為 javac
提供 -g
命令列選項,然後提供給 native-image
建置器。這會啟用原始碼層級的除錯,除錯器 (GDB) 會將機器指令與 Java 檔案中的特定原始碼行關聯起來。
將 -g
新增至 native-image
引數會產生除錯資訊。在原生可執行檔旁邊,會有一個 <executable_name>.debug 檔案,其中包含除錯資訊和一個 sources/ 目錄,其中包含 Java 原始碼檔案,除錯器會使用這些檔案來顯示行資訊的來源。例如
hello_image
hello_image.debug
sources
GDB 會自動載入給定原生可執行檔 <executable_name>
的 <executable_name>.debug 檔案。(原生可執行檔及其 *.debug 檔案之間有連結)
為了獲得更好的除錯體驗,我們建議將
-g
與-O0
結合使用。後者選項會停用 Graal 編譯器的內嵌和其他最佳化,否則這些最佳化會在除錯器中可觀察到(例如,除錯器可能會在行之間來回跳躍,而不是讓您從一行逐步執行到下一行)。同時,-O0
還會在編譯器中啟用額外的中繼資料收集,這有助於除錯器解析(例如)區域變數。
使用 GDB 和新的除錯資訊
建置資訊
*.debug 檔案包含有關建置的其他資訊,可以透過以下方式存取
readelf -p .debug.svm.imagebuild.classpath hello_image.debug
它會提供用於建置原生可執行檔的所有類別路徑項目的清單
String dump of section '.debug.svm.imagebuild.classpath':
[ 0] /home/user/.mx/cache/HAMCREST_e237ae735aac4fa5a7253ec693191f42ef7ddce384c11d29fbf605981c0be077d086757409acad53cb5b9e53d86a07cc428d459ff0f5b00d32a8cbbca390be49/hamcrest.jar
[ b0] /home/user/.mx/cache/JUNIT_5974670c3d178a12da5929ba5dd9b4f5ff461bdc1b92618c2c36d53e88650df7adbf3c1684017bb082b477cb8f40f15dcf7526f06f06183f93118ba9ebeaccce/junit.jar
[ 15a] /home/user/mx/mxbuild/jdk20/dists/jdk9/junit-tool.jar
[ 1a9] /home/user/graal/substratevm/mxbuild/jdk20/com.oracle.svm.test/bin
以下區段可供使用
- .debug.svm.imagebuild.classpath
- .debug.svm.imagebuild.modulepath
- .debug.svm.imagebuild.arguments
- .debug.svm.imagebuild.java.properties
main()
方法在哪裡?
使用
info functions ::main
尋找所有名為 main
的方法,然後使用 b <main method name>
,例如
(gdb) info functions ::main
All functions matching regular expression "::main":
File hello/Hello.java:
76: void hello.Hello::main(java.lang.String[]*);
File java/util/Timer.java:
534: void java.util.TimerThread::mainLoop();
(gdb) b 'hello.Hello::main'
Breakpoint 1 at 0x83c030: file hello/Hello.java, line 76.
設定中斷點
首先,找到您想要設定中斷點的方法類型,例如
(gdb) info types ArrayList
All types matching regular expression "ArrayList":
...
File java/util/ArrayList.java:
java.util.ArrayList;
java.util.ArrayList$ArrayListSpliterator;
java.util.ArrayList$Itr;
java.util.ArrayList$ListItr;
...
現在使用以下 GDB 自動完成
(gdb) b 'java.util.ArrayList::
現在按兩次 tab 鍵會顯示所有 ArrayList
方法可供選擇
java.util.ArrayList::ArrayList(int) java.util.ArrayList::iterator()
java.util.ArrayList::ArrayList(java.util.Collection*) java.util.ArrayList::lastIndexOf(java.lang.Object*)
java.util.ArrayList::add(int, java.lang.Object*) java.util.ArrayList::lastIndexOfRange(java.lang.Object*, int, int)
java.util.ArrayList::add(java.lang.Object*) java.util.ArrayList::listIterator()
java.util.ArrayList::add(java.lang.Object*, java.lang.Object[]*, int) java.util.ArrayList::listIterator(int)
java.util.ArrayList::addAll(int, java.util.Collection*) java.util.ArrayList::nBits(int)
java.util.ArrayList::addAll(java.util.Collection*) java.util.ArrayList::outOfBoundsMsg(int)
...
如果要使用
(gdb) b 'java.util.ArrayList::add`
則會在 add
的所有變體中安裝中斷點。
陣列
陣列具有 data
欄位,可以透過索引存取以取得個別的陣列元素,例如
Thread 1 "hello_image" hit Breakpoint 1, hello.Hello::main(java.lang.String[]*) (args=0x7ff33f800898) at hello/Hello.java:76
76 Greeter greeter = Greeter.greeter(args);
(gdb) p args
$1 = (java.lang.String[] *) 0x7ff33f800898
(gdb) p *args
$2 = {
<java.lang.Object> = {
<_objhdr> = {
hub = 0x1e37be0
}, <No data fields>},
members of java.lang.String[]:
len = 4,
data = 0x7ff33f8008a0
}
(gdb) p args.data
$3 = 0x7ff33f8008a0
(gdb) ptype args.data
type = class _z_.java.lang.String : public java.lang.String {
} *[0]
這裡可以透過索引存取 args.data
。
在此情況下,四個陣列元素的第一個是指向字串的指標
(gdb) p args.data[0]
$4 = (_z_.java.lang.String *) 0x27011a
字串
若要查看 Java 字串物件的實際內容,請查看其 value
欄位,例如
(gdb) p args.data[0]
$4 = (_z_.java.lang.String *) 0x27011a
args.data[0]
指向一個字串物件。讓我們取值
(gdb) p *args.data[0]
$5 = {
<java.lang.String> = {
<java.lang.Object> = {
<_objhdr> = {
hub = 0x1bb4780
}, <No data fields>},
members of java.lang.String:
value = 0x270118,
hash = 0,
coder = 0 '\000',
hashIsZero = false,
static CASE_INSENSITIVE_ORDER = 0x19d752,
...
static COMPACT_STRINGS = true
}, <No data fields>}
value
欄位保存字串資料。讓我們檢查 value
的類型
(gdb) p args.data[0].value
$3 = (_z_.byte[] *) 0x250119
value
的類型為 byte[]
。
如您先前所學,可以透過陣列的 data
欄位存取陣列的元素。
(gdb) p args.data[0].value.data
$10 = 0x7ff33f8008c8 "this\376\376\376\376\200G\273\001\030\001'"
GDB 很聰明,可以直接將位元組指標解釋為 C 字串。但本質上,它是一個陣列。以下內容提供我們來自 this
的 t
。
(gdb) p args.data[0].value.data[0]
$13 = 116 't'
最後一個字元之後出現垃圾的原因是 Java 字串值不是以 0 結尾(與 C 字串不同)。若要知道垃圾從何處開始,您可以檢查 len
欄位。
(gdb) p args.data[0].value.len
$14 = 4
向下轉型
假設您的來源使用靜態類型 Greeter
的變數,而您想要檢查其資料。
75 public static void main(String[] args) {
76 Greeter greeter = Greeter.greeter(args);
77 greeter.greet(); // Here you might have a NamedGreeter
如您所見,目前 GDB 只知道第 77 行中 greeter 的靜態類型
Thread 1 "hello_image" hit Breakpoint 2, hello.Hello::main(java.lang.String[]*) (args=<optimized out>) at hello/Hello.java:77
77 greeter.greet();
(gdb) p greeter
$17 = (hello.Hello$Greeter *) 0x7ff7f9101208
此外,您也無法看到僅存在於 NamedGreeter
子類別的欄位。
(gdb) p *greeter
$18 = {
<java.lang.Object> = {
<_objhdr> = {
hub = 0x1d1cae0
}, <No data fields>}, <No data fields>}
但您有 hub
欄位,它指向物件的類別物件。因此,它可讓您判斷位址 0x7ff7f9101208
的 Greeter 物件的執行階段類型
(gdb) p greeter.hub
$19 = (_z_.java.lang.Class *) 0x1d1cae0
(gdb) p *greeter.hub
$20 = {
<java.lang.Class> = {
<java.lang.Object> = {
<_objhdr> = {
hub = 0x1bec910
}, <No data fields>},
members of java.lang.Class:
typeCheckStart = 1188,
name = 0xb94a2, <<<< WE ARE INTERESTED IN THIS FIELD
superHub = 0x90202,
...
monitorOffset = 8,
optionalIdentityHashOffset = 12,
flags = 0,
instantiationFlags = 3 '\003'
}, <No data fields>}
(gdb) p greeter.hub.name
$21 = (_z_.java.lang.String *) 0xb94a2
(gdb) p greeter.hub.name.value.data
$22 = 0x7ff7f80705b8 "hello.Hello$NamedGreeter\351\001~*"
因此您知道該物件的實際類型是 hello.Hello$NamedGreeter
。
現在轉型為該類型
(gdb) set $rt_greeter = ('hello.Hello$NamedGreeter' *) greeter
現在您可以檢查向下轉型的方便變數 rt_greeter
(gdb) p $rt_greeter
$23 = (hello.Hello$NamedGreeter *) 0x7ff7f9101208
(gdb) p *$rt_greeter
$24 = {
<hello.Hello$Greeter> = {
<java.lang.Object> = {
<_objhdr> = {
hub = 0x1d1cae0
}, <No data fields>}, <No data fields>},
members of hello.Hello$NamedGreeter:
name = 0x270119
}
現在您可以看到僅存在於 NamedGreeter
子類型的 name
欄位。
(gdb) p $rt_greeter.name
$25 = (_z_.java.lang.String *) 0x270119
因此 name
欄位的類型為字串。您已經知道如何查看字串的內容
(gdb) p $rt_greeter.name.value.data
$26 = 0x7ff7f91008c0 "FooBar\376\376\200G\273\001\027\001'"
注意:如果要向下轉型的靜態類型是壓縮參考,則在向下轉型中使用的類型也必須是壓縮參考。
例如,如果您有
(gdb) p elementData.data[0]
$38 = (_z_.java.lang.Object *) 0x290fcc
在 ArrayList
的內部陣列中,第一個項目指向具有 _z_.
前置詞的 java.lang.Object
,這表示它是壓縮參考。
若要檢查該物件的執行階段類型是什麼,請使用
(gdb) p elementData.data[0].hub.name.value.data
$40 = 0x7ff7f8665600 "java.lang.String=\256\271`"
現在您知道壓縮參考實際上指向 java.lang.String
。
然後,當您轉型時,請不要忘記使用 _z_.
前置詞。
(gdb) p ('_z_.java.lang.String' *) elementData.data[0]
$41 = (_z_.java.lang.String *) 0x290fcc
(gdb) p *$41
$43 = {
<java.lang.String> = {
<java.lang.Object> = {
<_objhdr> = {
hub = 0x1bb4780
}, <No data fields>},
members of java.lang.String:
value = 0x290fce,
...
若要查看該字串的內容,請再次使用
(gdb) p $41.value.data
$44 = 0x7ff7f9207e78 "#subsys_name\thierarchy\tnum_cgroups\tenabled"
在實例方法中使用 this
變數
(gdb) bt
#0 hello.Hello$NamedGreeter::greet() (this=0x7ff7f9101208) at hello/Hello.java:71
#1 0x000000000083c060 in hello.Hello::main(java.lang.String[]*) (args=<optimized out>) at hello/Hello.java:77
#2 0x0000000000413355 in com.oracle.svm.core.JavaMainWrapper::runCore0() () at com/oracle/svm/core/JavaMainWrapper.java:178
#3 0x00000000004432e5 in com.oracle.svm.core.JavaMainWrapper::runCore() () at com/oracle/svm/core/JavaMainWrapper.java:136
#4 com.oracle.svm.core.JavaMainWrapper::doRun(int, org.graalvm.nativeimage.c.type.CCharPointerPointer*) (argc=<optimized out>, argv=<optimized out>) at com/oracle/svm/core/JavaMainWrapper.java:233
#5 com.oracle.svm.core.JavaMainWrapper::run(int, org.graalvm.nativeimage.c.type.CCharPointerPointer*) (argc=<optimized out>, argv=<optimized out>) at com/oracle/svm/core/JavaMainWrapper.java:219
#6 com.oracle.svm.core.code.IsolateEnterStub::JavaMainWrapper_run_e6899342f5939c89e6e2f78e2c71f5f4926b786d(int, org.graalvm.nativeimage.c.type.CCharPointerPointer*) (__0=<optimized out>, __1=<optimized out>)
at com/oracle/svm/core/code/IsolateEnterStub.java:1
(gdb) p this
$1 = (hello.Hello$NamedGreeter *) 0x7ff7f9001218
(gdb) p *this
$2 = {
<hello.Hello$Greeter> = {
<java.lang.Object> = {
<_objhdr> = {
hub = 0x1de2260
}, <No data fields>}, <No data fields>},
members of hello.Hello$NamedGreeter:
name = 0x25011b
}
(gdb) p this.name
$3 = (_z_.java.lang.String *) 0x270119
就像在 Java 或 C++ 程式碼中一樣,在實例方法中,不需要以 this.
為前置詞。
(gdb) p name
$7 = (_z_.java.lang.String *) 0x270119
(gdb) p name.value.data
$8 = 0x7ff7f91008c0 "FooBar\376\376\200G\273\001\027\001'"
存取靜態欄位
雖然在列印物件實例時會顯示靜態欄位,但您只是想查看特定靜態欄位的值。
(gdb) p 'java.math.BigDecimal::BIG_TEN_POWERS_TABLE'
$23 = (_z_.java.math.BigInteger[] *) 0x132b95
若要取得所有靜態欄位的清單,請使用
(gdb) info variables ::
檢查 .class
物件
對於影像中的每個 Java 類型,都有一種簡單的方法可以存取其類別物件(又稱為 hub)。
(gdb) info types PrintStream
All types matching regular expression "PrintStream":
...
File java/io/PrintStream.java:
java.io.PrintStream;
java.io.PrintStream$1;
...
若要存取 java.io.PrintStream
的 hub,您可以使用 .class
字尾
(gdb) p 'java.io.PrintStream.class'
$4 = {
<java.lang.Object> = {
<_objhdr> = {
hub = 0x1bec910
}, <No data fields>},
members of java.lang.Class:
typeCheckStart = 1340,
name = 0xbab58,
superHub = 0x901ba,
...
sourceFileName = 0xbab55,
classInitializationInfo = 0x14d189,
module = 0x14cd8d,
nestHost = 0xde78d,
simpleBinaryName = 0x0,
companion = 0x149856,
signature = 0x0,
...
}
這可讓您(例如)檢查 java.io.PrintStream
屬於哪個模組
(gdb) p 'java.io.PrintStream.class'.module.name.value.data
$12 = 0x7ff7f866b000 "java.base"
內嵌方法
在 PrintStream.writeln
中設定中斷點
(gdb) b java.io.PrintStream::writeln
Breakpoint 2 at 0x4080cb: java.io.PrintStream::writeln. (35 locations)
現在您導覽至
(gdb) bt
#0 java.io.BufferedWriter::min(int, int) (this=<optimized out>, a=8192, b=14) at java/io/BufferedWriter.java:216
#1 java.io.BufferedWriter::implWrite(java.lang.String*, int, int) (this=0x7ff7f884e828, s=0x7ff7f9101230, off=<optimized out>, len=<optimized out>) at java/io/BufferedWriter.java:329
#2 0x000000000084c50d in java.io.BufferedWriter::write(java.lang.String*, int, int) (this=<optimized out>, s=<optimized out>, off=<optimized out>, len=<optimized out>) at java/io/BufferedWriter.java:313
#3 0x0000000000901369 in java.io.Writer::write(java.lang.String*) (this=<optimized out>, str=<optimized out>) at java/io/Writer.java:278
#4 0x00000000008df465 in java.io.PrintStream::implWriteln(java.lang.String*) (this=0x7ff7f87e67b8, s=<optimized out>) at java/io/PrintStream.java:846
#5 0x00000000008e10a5 in java.io.PrintStream::writeln(java.lang.String*) (this=0x7ff7f87e67b8, s=<optimized out>) at java/io/PrintStream.java:826
#6 0x000000000083c00c in java.io.PrintStream::println(java.lang.String*) (this=<optimized out>, x=<optimized out>) at java/io/PrintStream.java:1168
#7 hello.Hello$NamedGreeter::greet() (this=<optimized out>) at hello/Hello.java:71
#8 0x000000000083c060 in hello.Hello::main(java.lang.String[]*) (args=<optimized out>) at hello/Hello.java:77
#9 0x0000000000413355 in com.oracle.svm.core.JavaMainWrapper::runCore0() () at com/oracle/svm/core/JavaMainWrapper.java:178
#10 0x00000000004432e5 in com.oracle.svm.core.JavaMainWrapper::runCore() () at com/oracle/svm/core/JavaMainWrapper.java:136
#11 com.oracle.svm.core.JavaMainWrapper::doRun(int, org.graalvm.nativeimage.c.type.CCharPointerPointer*) (argc=<optimized out>, argv=<optimized out>) at com/oracle/svm/core/JavaMainWrapper.java:233
#12 com.oracle.svm.core.JavaMainWrapper::run(int, org.graalvm.nativeimage.c.type.CCharPointerPointer*) (argc=<optimized out>, argv=<optimized out>) at com/oracle/svm/core/JavaMainWrapper.java:219
#13 com.oracle.svm.core.code.IsolateEnterStub::JavaMainWrapper_run_e6899342f5939c89e6e2f78e2c71f5f4926b786d(int, org.graalvm.nativeimage.c.type.CCharPointerPointer*) (__0=<optimized out>, __1=<optimized out>)
at com/oracle/svm/core/code/IsolateEnterStub.java:1
如果您查詢關於最上層框架的額外資訊,您會看到 min
已內嵌到 implWrite
中
(gdb) info frame
Stack level 0, frame at 0x7fffffffdb20:
rip = 0x84af8a in java.io.BufferedWriter::min(int, int) (java/io/BufferedWriter.java:216); saved rip = 0x84c50d
inlined into frame 1
source language unknown.
Arglist at unknown address.
Locals at unknown address, Previous frame's sp in rsp
現在逐步執行 min
的使用位置,您會看到值 14
是由 min
傳回(如預期)
(gdb) bt
#0 java.lang.String::getChars(int, int, char[]*, int) (this=0x7ff7f9101230, srcBegin=0, srcEnd=14, dst=0x7ff7f858ac58, dstBegin=0) at java/lang/String.java:1688
#1 java.io.BufferedWriter::implWrite(java.lang.String*, int, int) (this=0x7ff7f884e828, s=0x7ff7f9101230, off=<optimized out>, len=<optimized out>) at java/io/BufferedWriter.java:330
...
在除錯期間呼叫 svm_dbg_
協助程式函式
當以 -H:+IncludeDebugHelperMethods
建置影像時,會定義其他 @CEntryPoint
函式,可以從除錯期間的 GDB 呼叫,例如
(gdb) p greeter
$3 = (hello.Hello$Greeter *) 0x7ffff6881900
這裡您再次有一個名為 greeter
的區域變數,其靜態類型為 hello.Hello$Greeter
。若要查看其執行階段類型,您可以使用上述已描述的方法。
或者,您可以利用 svm_dbg_
協助程式函式。例如,在執行中的除錯工作階段中,您可以呼叫
void svm_dbg_print_hub(graal_isolatethread_t* thread, size_t hubPtr)
您必須傳遞 graal_isolatethread_t
的值和您要列印的 hub 絕對位址。在大多數情況下,graal_isolatethread_t
的值只是目前 IsolateThread
的值,可以在平台特定的暫存器中找到
平台 | 暫存器 |
---|---|
amd64 |
$r15 |
aarch64 |
$r28 |
最後,在您可以呼叫 svm_dbg_print_hub
之前,請確定您要列印的 hub 的絕對位址。使用
(gdb) p greeter.hub
$4 = (_z_.java.lang.Class *) 0x837820 <java.io.ObjectOutputStream::ObjectOutputStream(java.io.OutputStream*)+1120>
揭示在目前的情況下,greeter
中的 hub
欄位保存著 hub 的壓縮參考(hub-type
以 _z_.
為前置詞)。因此,您首先需要使用另一個 svm_dbg_
協助程式方法來取得 hub 欄位的絕對位址。
(gdb) call svm_dbg_obj_uncompress($r15, greeter.hub)
$5 = 140737339160608
(gdb) p/x $5
$6 = 0x7ffff71b7820
在呼叫 svm_dbg_obj_uncompress
的協助下,您現在知道 hub 位於位址 0x7ffff71b7820
,而且您終於可以呼叫 svm_dbg_print_hub
(gdb) call (void) svm_dbg_print_hub($r15, 0x7ffff71b7820)
hello.Hello$NamedGreeter
對 svm_dbg_
協助程式的兩個呼叫可以合併為單一行命令
(gdb) call (void) svm_dbg_print_hub($r15, svm_dbg_obj_uncompress($r15, greeter.hub))
hello.Hello$NamedGreeter
目前定義了以下 svm_dbg_
協助程式方法
int svm_dbg_ptr_isInImageHeap(graal_isolatethread_t* thread, size_t ptr);
int svm_dbg_ptr_isObject(graal_isolatethread_t* thread, size_t ptr);
int svm_dbg_hub_getLayoutEncoding(graal_isolatethread_t* thread, size_t hubPtr);
int svm_dbg_hub_getArrayElementSize(graal_isolatethread_t* thread, size_t hubPtr);
int svm_dbg_hub_getArrayBaseOffset(graal_isolatethread_t* thread, size_t hubPtr);
int svm_dbg_hub_isArray(graal_isolatethread_t* thread, size_t hubPtr);
int svm_dbg_hub_isPrimitiveArray(graal_isolatethread_t* thread, size_t hubPtr);
int svm_dbg_hub_isObjectArray(graal_isolatethread_t* thread, size_t hubPtr);
int svm_dbg_hub_isInstance(graal_isolatethread_t* thread, size_t hubPtr);
int svm_dbg_hub_isReference(graal_isolatethread_t* thread, size_t hubPtr);
long long int svm_dbg_obj_getHub(graal_isolatethread_t* thread, size_t objPtr);
long long int svm_dbg_obj_getObjectSize(graal_isolatethread_t* thread, size_t objPtr);
int svm_dbg_obj_getArrayElementSize(graal_isolatethread_t* thread, size_t objPtr);
long long int svm_dbg_obj_getArrayBaseOffset(graal_isolatethread_t* thread, size_t objPtr);
int svm_dbg_obj_isArray(graal_isolatethread_t* thread, size_t objPtr);
int svm_dbg_obj_isPrimitiveArray(graal_isolatethread_t* thread, size_t objPtr);
int svm_dbg_obj_isObjectArray(graal_isolatethread_t* thread, size_t objPtr);
int svm_dbg_obj_isInstance(graal_isolatethread_t* thread, size_t objPtr);
int svm_dbg_obj_isReference(graal_isolatethread_t* thread, size_t objPtr);
long long int svm_dbg_obj_uncompress(graal_isolatethread_t* thread, size_t compressedPtr);
long long int svm_dbg_obj_compress(graal_isolatethread_t* thread, size_t objPtr);
int svm_dbg_string_length(graal_isolatethread_t* thread, size_t strPtr);
void svm_dbg_print_hub(graal_isolatethread_t* thread, size_t hubPtr);
void svm_dbg_print_obj(graal_isolatethread_t* thread, size_t objPtr);
void svm_dbg_print_string(graal_isolatethread_t* thread, size_t strPtr);
void svm_dbg_print_fatalErrorDiagnostics(graal_isolatethread_t* thread, size_t sp, void * ip);
void svm_dbg_print_locationInfo(graal_isolatethread_t* thread, size_t mem);