◀戻る
GDB を使用したネイティブ実行ファイルのデバッグ
どの GDB を使用するべきか?
- GDB 10.2 以降を使用してください。デバッグ情報は、
mx debuginfotest
を使用して 10.2 に対してテストされています。 - 新しいバージョンでは、デバッガ出力のフォーマットがわずかに異なる場合があります(たとえば、CI/CD ゲートチェックが失敗する原因となる可能性があります)。
- 最近の Linux ディストリビューションにバンドルされている GDB は、デバッグセッションに問題なく動作します。
デバッグ情報を含むネイティブ実行ファイルのビルド
デバッグ情報を含むネイティブ実行ファイルをビルドするには、アプリケーションのコンパイル時に `javac` に `-g` コマンドラインオプションを、その後 `native-image` ビルダに指定します。これによりソースレベルデバッグが有効になり、デバッガ(GDB)は機械命令とJavaファイル内の特定のソース行を関連付けます。
`native-image` の引数に `-g` を追加すると、デバッグ情報が生成されます。ネイティブ実行ファイルの横に、デバッグ情報を含む *`
hello_image
hello_image.debug
sources
GDB は、指定されたネイティブ実行ファイル `
より良いデバッグ体験を得るために、`-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
(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 キーを2回押すと、選択可能なすべての `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` にインデックスを使用してアクセスできます。
この場合、4つの配列要素の最初のものは、Stringへのポインタです。
(gdb) p args.data[0]
$4 = (_z_.java.lang.String *) 0x27011a
文字列
Java String オブジェクトの実際のコンテンツを確認するには、**`value` フィールド**を確認します。例:
(gdb) p args.data[0]
$4 = (_z_.java.lang.String *) 0x27011a
`args.data[0]` は String オブジェクトを指しています。参照外ししてみましょう。
(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` フィールドには String データが格納されています。`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 の String の値は(C 文字列とは異なり)0 で終了しないためです。ゴミが始まる場所を知るには、`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` フィールドの型は String です。String の内容の確認方法は既に知っています。
(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,
...
その String の内容を確認するには、もう一度
(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 型には、そのクラスオブジェクト(別名ハブ)に簡単にアクセスできる方法があります。
(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` のハブにアクセスするには、`.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` でイメージがビルドされると、デバッグ中に GDB から呼び出すことができる追加の `@CEntryPoint` 関数が定義されます。例:
(gdb) p greeter
$3 = (hello.Hello$Greeter *) 0x7ffff6881900
ここでも、静的型が `hello.Hello$Greeter` のローカル変数 `greeter` があります。その実行時型を確認するには、既に説明したメソッドを使用できます。
あるいは、`svm_dbg_` ヘルパー関数を使用することもできます。たとえば、実行中のデバッグセッションから、
void svm_dbg_print_hub(graal_isolatethread_t* thread, size_t hubPtr)
`graal_isolatethread_t` の値と、出力するハブの絶対アドレスを渡す必要があります。ほとんどの場合、`graal_isolatethread_t` の値は、プラットフォーム固有のレジスタにある現在の `IsolateThread` の値です。
プラットフォーム | レジスタ |
---|---|
amd64 |
$r15 |
aarch64 |
$r28 |
最後に、`svm_dbg_print_hub` を呼び出す前に、出力するハブの**絶対アドレス**を確認してください。使用
(gdb) p greeter.hub
$4 = (_z_.java.lang.Class *) 0x837820 <java.io.ObjectOutputStream::ObjectOutputStream(java.io.OutputStream*)+1120>
現在の状況では、`greeter` の `hub` フィールドにはハブへの圧縮参照が含まれていることがわかります(`hub-type` には `_z_.` プレフィックスが付いています)。したがって、まず別の `svm_dbg_` ヘルパーメソッドを使用して、ハブフィールドの絶対アドレスを取得する必要があります。
(gdb) call svm_dbg_obj_uncompress($r15, greeter.hub)
$5 = 140737339160608
(gdb) p/x $5
$6 = 0x7ffff71b7820
`svm_dbg_obj_uncompress` を呼び出すことで、ハブはアドレス `0x7ffff71b7820` にあることがわかり、最後に `svm_dbg_print_hub` を呼び出すことができます。
(gdb) call (void) svm_dbg_print_hub($r15, 0x7ffff71b7820)
hello.Hello$NamedGreeter
`svm_dbg_` ヘルパーの両方の呼び出しを1つのコマンドラインにまとめることができます。
(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);