1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
|
---
title: ハッキングのヒント
slug: Mozilla/Projects/SpiderMonkey/Hacking_Tips
tags:
- SpiderMonkey
translation_of: Mozilla/Projects/SpiderMonkey/Hacking_Tips
---
<div>{{SpiderMonkeySidebar("Getting_started")}}</div>
<p>このページには、SpiderMonkey に関連する問題の調査に役立つヒントがいくつか掲載されています。ここに記載されているヒントはすべて、<a href="/ja/docs/Mozilla/Projects/SpiderMonkey">SpiderMonkey のビルドドキュメント</a>の最後に取得された JavaScript シェルを扱っています。2つの部分に分かれており、1つはデバッグに関するセクション、もう1つはドラフト最適化に関するセクションです。これらのヒントの多くは、JS シェルのデバッグビルドにのみ適用されます。それらはリリースビルドでは機能しません。</p>
<h2 id="デバッグのヒント">デバッグのヒント</h2>
<h3 id="ヘルプの入手_(JS_シェルから)">ヘルプの入手 (JS シェルから)</h3>
<p><strong>help</strong> 関数を使用して、シェルのすべてのプリミティブ関数のリストを記述します。いくつかの関数は 'os' オブジェクトの下に移動されているので、<strong>help(os)</strong> はその "namespace" のメンバーだけを簡単に助けます。</p>
<h3 id="関数のバイトコードを取得する_(JSシェルから)">関数のバイトコードを取得する (JSシェルから)</h3>
<p>シェルには、関数のバイトコードをソースノートとともにダンプするための小さな関数 <strong>dis</strong> があります。引数がなければ、呼び出し元のバイトコードをダンプします。</p>
<pre class="eval"><code>js> function f () {
return 1;
}
js> <strong>dis(</strong>f<strong>)</strong>;
flags:
loc op
----- --
main:
00000: one
00001: return
00002: stop
Source notes:
ofs line pc delta desc args
---- ---- ----- ------ -------- ------
0: 1 0 [ 0] newline
1: 2 0 [ 0] colspan 2
3: 2 2 [ 2] colspan 9
</code></pre>
<h3 id="関数のバイトコードを取得する_(gdbから)">関数のバイトコードを取得する (gdbから)</h3>
<p><em>jsopcode.cpp</em> では、<strong>js::DisassembleAtPC</strong> という名前の関数が使用されています。PC はスクリプトのバイトコードを出力できます。<strong>js::DumpScript</strong> などのこの関数のいくつかの変種はデバッグに便利です。</p>
<h3 id="JS_スタックのプリント_(gdbから)">JS スタックのプリント (gdbから)</h3>
<p>jsobj.cppでは、<strong>js::DumpBacktrace</strong> という名前の関数が、JS スタックのバックトレース à la gdb を出力します。バックトレースには次の順序で、スタックの深さ、インタープリタフレームポインタ (<em>js/src/vm/Stack.h</em>、<strong>StackFrame</strong> クラスを参照) または IonMonkeyでコンパイルした場合は (nil)、コールロケーションのファイルと行番号、およびカッコの下に実行された JSScript ポインタと jsbytecode ポインタ (pc) が含まれます。</p>
<pre class="eval"><code>$ gdb --args js
[…]
(gdb) b js::ReportOverRecursed
(gdb) r
js> function f(i) {
if (i % 2) f(i + 1);
else f(i + 3);
}
js> f(0)
Breakpoint 1, js::ReportOverRecursed (maybecx=0xfdca70) at /home/nicolas/mozilla/ionmonkey/js/src/jscntxt.cpp:495
495 if (maybecx)
(gdb) call <strong>js::DumpBacktrace(</strong>maybecx<strong>)</strong>
#0 (nil) typein:2 (0x7fffef1231c0 @ 0)
#1 (nil) typein:2 (0x7fffef1231c0 @ 24)
#2 (nil) typein:3 (0x7fffef1231c0 @ 47)
#3 (nil) typein:2 (0x7fffef1231c0 @ 24)
#4 (nil) typein:3 (0x7fffef1231c0 @ 47)
[…]
#25157 0x7fffefbbc250 typein:2 (0x7fffef1231c0 @ 24)
#25158 0x7fffefbbc1c8 typein:3 (0x7fffef1231c0 @ 47)
#25159 0x7fffefbbc140 typein:2 (0x7fffef1231c0 @ 24)
#25160 0x7fffefbbc0b8 typein:3 (0x7fffef1231c0 @ 47)
#25161 0x7fffefbbc030 typein:5 (0x7fffef123280 @ 9)
</code></pre>
<p>Note, you can do the exact same exercise above using <code>lldb</code> (necessary on OSX after Apple removed <code>gdb</code>) by running <code>lldb -f js</code> then following the remaining steps.</p>
<p>Since SpiderMonkey 48, we have a gdb unwinder. This unwinder is able to read the frames created by the JIT, and to display the frames which are after these JIT frames.</p>
<pre><code>$ gdb --args out/dist/bin/js ./foo.js
[…]
SpiderMonkey unwinder is disabled by default, to enable it type:
enable unwinder .* SpiderMonkey
(gdb) b js::math_cos
(gdb) run
[…]
#0 js::math_cos (cx=0x14f2640, argc=1, vp=0x7fffffff6a88) at js/src/jsmath.cpp:338
338 CallArgs args = CallArgsFromVp(argc, vp);
(gdb) enable unwinder .* SpiderMonkey
(gdb) backtrace 10
#0 0x0000000000f89979 in js::math_cos(JSContext*, unsigned int, JS::Value*) (cx=0x14f2640, argc=1, vp=0x7fffffff6a88) at js/src/jsmath.cpp:338
#1 0x0000000000ca9c6e in js::CallJSNative(JSContext*, bool (*)(JSContext*, unsigned int, JS::Value*), JS::CallArgs const&) (cx=0x14f2640, native=0xf89960 , args=...) at js/src/jscntxtinlines.h:235
#2 0x0000000000c87625 in js::Invoke(JSContext*, JS::CallArgs const&, js::MaybeConstruct) (cx=0x14f2640, args=..., construct=js::NO_CONSTRUCT) at js/src/vm/Interpreter.cpp:476
#3 0x000000000069bdcf in js::jit::DoCallFallback(JSContext*, js::jit::BaselineFrame*, js::jit::ICCall_Fallback*, uint32_t, JS::Value*, JS::MutableHandleValue) (cx=0x14f2640, frame=0x7fffffff6ad8, stub_=0x1798838, argc=1, vp=0x7fffffff6a88, res=JSVAL_VOID) at js/src/jit/BaselineIC.cpp:6113
#4 0x00007ffff7f41395 in </code><<JitFrame_Exit>><code> ()
#5 0x00007ffff7f42223 in </code><<JitFrame_BaselineStub>><code> ()
#6 0x00007ffff7f4423d in </code><<JitFrame_BaselineJS>><code> ()
#7 0x00007ffff7f4222e in </code><<JitFrame_BaselineStub>><code> ()
#8 0x00007ffff7f4326a in </code><<JitFrame_BaselineJS>><code> ()
#9 0x00007ffff7f38d5f in </code><<JitFrame_Entry>><code> ()
#10 0x00000000006a86de in EnterBaseline(JSContext*, js::jit::EnterJitData&) (cx=0x14f2640, data=...) at js/src/jit/BaselineJIT.cpp:150
</code></pre>
<p>Note, when you enable the unwinder, the current version of gdb (7.10.1) does not flush the backtrace. Therefore, the JIT frames do not appear until you settle on the next breakpoint. To work-around this issue you can use the recording feature of <code>gdb</code>, to step one instruction, and settle back to where you came from with the following set of <code>gdb</code> commands:</p>
<pre><code>(gdb) record full
(gdb) si
(gdb) record goto 0
(gdb) record stop
</code></pre>
<p>If you have a core file, you can use the gdb unwinder the same way, or do everything from the command line as follow:</p>
<pre><code>$ gdb -ex 'enable unwinder .* SpiderMonkey' -ex 'bt 0' -ex 'thread apply all backtrace' -ex 'quit' out/dist/bin/js corefile
</code></pre>
<p>The gdb unwinder is supposed to be loaded by <code>dist/bin/js-gdb.py</code> and load python scripts which are located in <code>js/src/gdb/mozilla</code> under gdb. If gdb does not load the unwinder by default, you can force it to, by using the <code>source</code> command with the <code>js-gdb.py</code> file.</p>
<h3 id="生成されたコードにブレークポイントを設定する_(gdb_x86_x86-64_arm_から)">生成されたコードにブレークポイントを設定する (gdb, x86 / x86-64, arm から)</h3>
<p>To set a breakpoint the generated code of a specific JSScript compiled with IonMonkey. Set a breakpoint on the instruction you are interested in. If you have no precise idea which function you are looking at, you can set a breakpoint on the <strong>js::ion::CodeGenerator::visitStart</strong> function. Optionally, a condition on the <strong>ins->id()</strong> of the LIR instruction can be added to select precisely the instruction you are looking for. Once the breakpoint is on <strong>CodeGenerator</strong> function of the LIR instruction, add a command to generate a static breakpoint in the generated code.</p>
<pre class="eval"><code>$ gdb --args js
[…]
(gdb) b js::ion::CodeGenerator::visitStart
(gdb) command
>call masm.breakpoint()
>continue
>end
(gdb) r
js> function f(a, b) { return a + b; }
js> for (var i = 0; i < 100000; i++) f(i, i + 1);
Breakpoint 1, js::ion::CodeGenerator::visitStart (this=0x101ed20, lir=0x10234e0)
at /home/nicolas/mozilla/ionmonkey/js/src/ion/CodeGenerator.cpp:609
609 }
Program received signal SIGTRAP, Trace/breakpoint trap.
0x00007ffff7fb165a in ?? ()
(gdb)
</code></pre>
<p>Once you hit the generated breakpoint, you can replace it by a gdb breakpoint to make it conditional, the procedure is to first replace the generated breakpoint by a nop instruction, and to set a breakpoint at the address of the nop.</p>
<pre class="eval"><code>(gdb) x /5i $pc - 1
0x7ffff7fb1659: int3
=> 0x7ffff7fb165a: mov 0x28(%rsp),%rax
0x7ffff7fb165f: mov %eax,%ecx
0x7ffff7fb1661: mov 0x30(%rsp),%rdx
0x7ffff7fb1666: mov %edx,%ebx
(gdb) # replace the int3 by a nop
(gdb) set *(unsigned char *) ($pc - 1) = 0x90
(gdb) x /1i $pc - 1
0x7ffff7fb1659: nop
(gdb) # set a breakpoint at the previous location
(gdb) b *0x7ffff7fb1659
Breakpoint 2 at 0x7ffff7fb1659
</code>
</pre>
<h3 id="イオン生成アセンブリコードのプリント_(gdb_から)">イオン生成アセンブリコードのプリント (gdb から)</h3>
<p>If you want to look at the assembly code generated by IonMonkey, you can follow this procedure:</p>
<ul>
<li>Place a breakpoint at CodeGenerator.cpp on the CodeGenerator::link method.</li>
<li>Step next a few times, so that the "code" variable gets generated</li>
<li>Print code->code_, which is the address of the code</li>
<li>Disassembly code read at this address (using x/Ni address, where N is the number of instructions you would like to see)</li>
</ul>
<p>Here is an example. It might be simpler to use the CodeGenerator::link lineno instead of the full qualified name to put the breakpoint. Let's say that the line number of this function is 4780, for instance:</p>
<pre>(gdb) b CodeGenerator.cpp:4780
Breakpoint 1 at 0x84cade0: file /home/code/mozilla-central/js/src/ion/CodeGenerator.cpp, line 4780.
(gdb) r
Starting program: /home/code/mozilla-central/js/src/32-release/js -f /home/code/jaeger.js
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
[New Thread 0xf7903b40 (LWP 12563)]
[New Thread 0xf6bdeb40 (LWP 12564)]
Run#0
Breakpoint 1, js::ion::CodeGenerator::link (this=0x86badf8)
at /home/code/mozilla-central/js/src/ion/CodeGenerator.cpp:4780
4780 {
(gdb) n
4781 JSContext *cx = GetIonContext()->cx;
(gdb) n
4783 Linker linker(masm);
(gdb) n
4784 IonCode *code = linker.newCode(cx, JSC::ION_CODE);
(gdb) n
4785 if (!code)
(gdb) p code->code_
$1 = (uint8_t *) 0xf7fd25a8 "\201", <incomplete sequence \354\200>
(gdb) x/2i 0xf7fd25a8
0xf7fd25a8: sub $0x80,%esp
0xf7fd25ae: mov 0x94(%esp),%ecx
</pre>
<p>On arm, the compiled JS code will always be ARM machine code, whereas spidermonkey itself is frequently Thumb2. Since there isn't debug info for the jitted code, you will need to tell gdb that you are looking at ARM code:</p>
<pre>(gdb) set arm force-mode arm
</pre>
<p>Or you can wrap the x command in your own command:</p>
<pre>def xi
set arm force-mode arm
eval "x/%di %d", $arg0, $arg1
set arm force-mode auto
end
</pre>
<h3 id="Printing_asm.jswasm_generated_assembly_code_(from_gdb)">Printing asm.js/wasm generated assembly code (from gdb)</h3>
<ul>
<li>
<p>Set a breakpoint on <code>js::wasm::Instance::callExport</code> (defined in <code>WasmInstance.cpp</code> as of November 18th 2016). This will trigger for *any* asm.js/wasm call, so you should find a way to set this breakpoint for the only generated codes you want to look at.</p>
</li>
<li>
<p>Run the program.</p>
</li>
<li>
<p>Do <code>next</code> in gdb until you reach the definition of the <code>funcPtr</code>:</p>
</li>
</ul>
<pre>// Call the per-exported-function trampoline created by GenerateEntry. │
auto funcPtr = JS_DATA_TO_FUNC_PTR(ExportFuncPtr, codeBase() + func.entryOffset()); │
if (!CALL_GENERATED_2(funcPtr, exportArgs.begin(), &tlsData_)) │
return false; </pre>
<ul>
<li><code>After it's set, x/64i funcPtr</code> will show you the trampoline code. There should be a call to an address at some point ; that's what we're targeting. Copy that address.</li>
</ul>
<pre><code> 0x7ffff7ff6000: push %r15
0x7ffff7ff6002: push %r14
0x7ffff7ff6004: push %r13
0x7ffff7ff6006: push %r12
0x7ffff7ff6008: push %rbp
0x7ffff7ff6009: push %rbx
0x7ffff7ff600a: movabs $0xea4f80,%r10
0x7ffff7ff6014: mov 0x178(%r10),%r10
0x7ffff7ff601b: mov %rsp,0x40(%r10)
0x7ffff7ff601f: mov (%rsi),%r15
0x7ffff7ff6022: mov %rdi,%r10
0x7ffff7ff6025: push %r10
0x7ffff7ff6027: test $0xf,%spl
0x7ffff7ff602b: je 0x7ffff7ff6032
0x7ffff7ff6031: int3
0x7ffff7ff6032: callq 0x7ffff7ff5000 <------ right here</code></pre>
<ul>
<li><code>x/64i address</code> (in this case: <code>x/64i 0x7ffff7ff6032</code>).</li>
<li>If you want to put a breakpoint at the function's entry, you can do: <code>b *address </code>(for instance here, <code>b* 0x7ffff7ff6032</code>). Then you can display the instructions around pc with<code> x/20i $pc, </code>and execute instruction by instruction with<code> stepi.</code></li>
</ul>
<h3 id="Finding_the_script_of_Ion_generated_assembly_(from_gdb)">Finding the script of Ion generated assembly (from gdb)</h3>
<p>When facing a bug in which you are in the middle of IonMonkey generated code, first thing to note, is that gdb's backtrace is not reliable, because the generated code does not keep a frame pointer. To figure it out you have to read the stack to infer the IonMonkey frame.</p>
<pre><code>(gdb) </code>x /64a $sp
[…]
0x7fffffff9838: 0x7ffff7fad2da 0x141
0x7fffffff9848: 0x7fffef134d40 0x2
[…]
(gdb) p (*(JSFunction**) 0x7fffffff9848)->u.i.script_->lineno
$1 = 1
(gdb) p (*(JSFunction**) 0x7fffffff9848)->u.i.script_->filename
$2 = 0xff92d1 "typein"
</pre>
<p>The stack is order as defined in js/src/ion/IonFrames-x86-shared.h, it is composed of the return address, a descriptor (a small value), the JSFunction (if it is even) or a JSScript (if the it is odd, remove it to dereference the pointer) and the frame ends with the number of actual arguments (a small value too). If you want to know at which LIR the code is failing at, the <strong>js::ion::CodeGenerator::generateBody</strong> function can be intrumented to dump the LIR <strong>id</strong> before each instruction.</p>
<pre><code>for (; iter != current->end(); iter++) {
IonSpew(IonSpew_Codegen, "instruction %s", iter->opName());
[…]
masm.store16(Imm32(iter->id()), Address(StackPointer, -8)); // added
if (!iter->accept(this))
return false;
</code>
<code> […]
</code><code>}</code></pre>
<p><code>This modification will add an instruction which abuse the stack pointer </code>to store an immediate value (the LIR id) to a location which would never be generated by any sane compiler. Thus when dumping the assembly under gdb, this kind of instructions would be easily noticeable.</p>
<h3 id="Viewing_the_MIRGraph_of_IonOdin_compilations_(from_gdb)">Viewing the MIRGraph of Ion/Odin compilations (from gdb)</h3>
<p>With gdb instrumentation, we can call <a href="https://github.com/sstangl/iongraph">iongraph</a> program within gdb when the execution is stopped. This instrumentation adds an <strong><code>iongraph</code></strong> command when provided with an instance of a <strong><code>MIRGenerator*</code></strong>, will call <code>iongraph</code>, <code>graphviz</code> and your prefered png viewer to display the MIR graph at the precise time of the execution. To find <strong><code>MIRGenetator*</code></strong> instances, is best is to look up into the stack for <code>OptimizeMIR</code>, or <code>CodeGenerator::generateBody</code>. <strong><code>OptimizeMIR</code></strong> function has a <strong><code>mir</code></strong> argument, and the <strong><code>CodeGenerator::generateBody</code></strong> function has a member <strong><code>this->gen</code></strong>.</p>
<pre><code>(gdb) bt</code>
#0 0x00000000007eaad4 in js::InlineList<js::jit::MBasicBlock>::begin() const (this=0x33dbbc0) at …/js/src/jit/InlineList.h:280
#1 0x00000000007cb845 in js::jit::MIRGraph::begin() (this=0x33dbbc0) at …/js/src/jit/MIRGraph.h:787
#2 0x0000000000837d25 in js::jit::BuildPhiReverseMapping(js::jit::MIRGraph&) (graph=...) at …/js/src/jit/IonAnalysis.cpp:2436
#3 0x000000000083317f in js::jit::OptimizeMIR(js::jit::MIRGenerator*) (mir=0x33dbdf0) at …/js/src/jit/Ion.cpp:1570
…
(gdb) frame 3
#3 0x000000000083317f in js::jit::OptimizeMIR(js::jit::MIRGenerator*) (mir=0x33dbdf0) at …/js/src/jit/Ion.cpp:1570
<code>(gdb) iongraph mir</code>
function 0 (asm.js compilation): success; 1 passes<code>.</code>
<code>/* open your png viewer with the result of iongraph */</code></pre>
<p>This gdb instrumentation is supposed to work with debug builds, or with optimized build compiled with <code>--enable-jitspew</code> configure flag. External programs such as <code>iongraph</code>, <code>dot</code>, and your png viewer are search into the <code>PATH</code>, otherwise custom one can either be configured with environment variables (<code>GDB_IONGRAPH</code>, <code>GDB_DOT</code>, <code>GDB_PNGVIEWER</code>) before starting gdb, or with gdb parameters (<code>set iongraph-bin <path></code>, <code>set dot-bin <path></code>, <code>set pngviewer-bin <path></code>) within gdb.</p>
<p>Enabling GDB instrumentation may require launching a JS shell executable that shares a directory with a file name "js-gdb.py". If js/src/js does not provide the "iongraph" command, try js/src/shell/js. GDB may complain that ~/.gdbinit requires modification to authorize user scripts, and if so will print out directions.</p>
<h3 id="Break_on_valgrind_errors">Break on valgrind errors</h3>
<p>Sometimes, a bug can be reproduced under valgrind but hardly under gdb. One way to investigate is to let valgrind start gdb for you, the other way documented here is to let valgrind act as a gdb server which can be manipulated from the gdb remote.</p>
<pre><code>$ valgrind --smc-check=all-non-file</code> --vex-iropt-register-updates=allregs-at-mem-access <code>--vgdb-error=0 ./js …</code></pre>
<p>This command will tell you how to start gdb as a remote. Be aware that functions which are usually dumping some output will do it in the shell where valgrind is started and not in the shell where gdb is started. Thus functions such as <strong>js::DumpBacktrace</strong>, when called from gdb, will print their output in the shell containing valgrind.</p>
<h3 id="Adding_spew_for_Compilations_Bailouts_Invalidations_(from_gdb)">Adding spew for Compilations & Bailouts & Invalidations (from gdb)</h3>
<p>If you are in rr, and forgot to record with the spew enabled with IONFLAGS or because this is an optimized build, then you can add similar spew with extra breakpoints within gdb. gdb has the ability to set breakpoints with commands, but a simpler / friendlier version is to use <strong>dprintf</strong>, with a location, and followed by printf-like arguments.</p>
<pre>(gdb) dprintf js::jit::IonBuilder::IonBuilder, "Compiling %s:%d:%d-%d\n", info->script_->scriptSource()->filename_.mTuple.mFirstA, info->script_->lineno_, info->script_->sourceStart_, info->script_->sourceEnd_
Dprintf 1 at 0x7fb4f6a104eb: file /home/nicolas/mozilla/contrib-push/js/src/jit/IonBuilder.cpp, line 159.
(gdb) cond 1 inliningDepth == 0
(gdb) dprintf js::jit::BailoutIonToBaseline, "Bailout from %s:%d:%d-%d\n", iter.script()->scriptSource()->filename_.mTuple.mFirstA, iter.script()->lineno_, iter.script()->sourceStart_, iter.script()->sourceEnd_
Dprintf 2 at 0x7fb4f6fe43dc: js::jit::BailoutIonToBaseline. (2 locations)
(gdb) dprintf Ion.cpp:3196, "Invalidate %s:%d:%d-%d\n", co->script_->scriptSource()->filename_.mTuple.mFirstA, co->script_->lineno_, co->script_->sourceStart_, co->script_->sourceEnd_
Dprintf 3 at 0x7fb4f6a0b62a: file /home/nicolas/mozilla/contrib-push/js/src/jit/Ion.cpp, line 3196.
<code>(gdb) continue</code>
Compiling self-hosted:650:20470-21501
Bailout from self-hosted:20:403-500
Invalidate self-hosted:20:403-500
</pre>
<p>Note: the line 3196, listed above, corresponds to the location of the <a href="http://searchfox.org/mozilla-central/rev/f6c298b36db67a7109079c0dd7755f329c1d58e2/js/src/jit/Ion.cpp#3196">Jit spew inside jit::Invalidate function</a>.</p>
<h2 id="ハッキングのヒント">ハッキングのヒント</h2>
<h3 id="Benchmarking_(shell)">Benchmarking (shell)</h3>
<p><a href="http://arewefastyet.com">AreWeFastYet.com</a> display the benchmark results of the JavaScript shell, and browser for B2G. These benchmarks are publicly recognized benchmarks suggested by other companies and are used as a metric to evaluate how fast JavaScript engines. This tool is maintained by the JavaScript Team, to find regressions and to compare SpiderMonkey with other JavaScript engines when possible. To run these benchmarks localy, you can clone <a href="https://github.com/haytjes/arewefastyet.git">AreWeFastYet sources</a> and look inside the <em>benchmarks</em> directory to run individual benchmarks with your JS shell.</p>
<h3 id="Using_the_Gecko_Profiler_(browser_xpcshell)">Using the Gecko Profiler (browser / xpcshell)</h3>
<p>see the section dedicated to <a href="/en-US/docs/Performance/Profiling_with_the_Built-in_Profiler" title="/en-US/docs/Performance/Profiling_with_the_Built-in_Profiler">profiling with the gecko profiler</a>. This method of profiling has the advantage of mixing the JavaScript stack with the C++ stack, which is useful to analyze library function issues. One tip is to start looking at a script with an inverted JS stack to locate the most expensive JS function, then to focus on the frame of this JS function, and to remove the inverted stack and look at C++ part of this function to determine from where the cost is coming from.</p>
<h3 id="Using_the_JIT_Inspector_(browser)">Using the JIT Inspector (browser)</h3>
<p>Install the <a href="https://addons.mozilla.org/en-US/firefox/addon/jit-inspector/" title="https://addons.mozilla.org/en-US/firefox/addon/jit-inspector/">JIT Inspector</a> addon in your browser. This addon provides estimated cost of IonMonkey , the Baseline compiler, and the interpreter. In addition it provides a clean way to analyze if instructions are inferred as being monomorphic or polymorphic in addition to the number of time each category of type has been observed.</p>
<h3 id="Using_the_TraceLogger_(JS_shell_browser)">Using the TraceLogger (JS shell / browser)</h3>
<p>Create graphs showing time spent in which engine and which function like <a href="https://raw.githubusercontent.com/h4writer/tracelogger/master/screenshot.png" title="http://alasal.be/ionmonkey/index.php?subject=octane-richards-82d28bdf9317">this</a>.</p>
<p>Whenever running a testcase the file "tl-data.json" and several "tl-*" files get created in the "/tmp" directory. (Per proces a "tl-data-*PID*.json" file and per thread a "tl-tree.*PID*.*ID*.tl", "tl-event.*PID*.*ID*.tl" and "tl-dict.*PID*.*ID*.json" file). These files contain all information to create a tracelogger graph. On <a href="https://github.com/h4writer/tracelogger">https://github.com/h4writer/tracelogger</a> you can find the instructions to create the graph (Tools V2 > 1. Creating a tracelogging graph).</p>
<p>Note 1: when you are doing this from "file:///" you will probably get a security warning in the console. This is because firefox doesn't allow loading files from the harddisk using httprequest, even when the file loading the file is on the harddisk. There are two solutions. One is to create a localhost server and serving the files there. The simplest way to do this is to run <code>python -m SimpleHTTPServer</code> from within the above directory. The other being disable this check in "about:config", by temporarily switching "security.fileuri.strict_origin_policy" to false</p>
<p>Note 2: The files can be very big and take a long time to load in the browser. Therefore it might be good to reduce the logged file. This will remove entries that took only a minor time (=entries that will only show up with les than 1px). This can be done with the reduce.py script in <a href="https://github.com/haytjes/tracelogger/tree/master/tools_v2" title="https://github.com/haytjes/tracelogger">https://github.com/haytjes/tracelogger/tree/master/tools_v2</a>. You need to download "engine.js", "reduce.py", "reduce.js", "reduce-tree.js" and "reduce-corrections.js". Running this tool is a matter of running "python reduce.py JS_SHELL /tmp/tl-data.json tl-reduced". Where JS_SHELL is a real shell.</p>
<h3 id="Using_callgrind_(JS_shell)">Using callgrind (JS shell)</h3>
<p>As SpiderMonkey just-in-time compiler are rewriting the executed program, valgrind should be informed from the command line by adding <strong>--smc-check=all-non-file</strong>.</p>
<pre class="eval"><code>$ valgrind --tool=callgrind --callgrind-out-file=bench.clg \
--smc-check=all-non-file</code> --vex-iropt-register-updates=allregs-at-mem-access<code> ./js ./run.js
</code></pre>
<p>The output file can then be use with <strong>kcachegrind</strong> which provides a graphical view of the call graph.</p>
<h3 id="Using_IonMonkey_spew_(JS_shell)">Using IonMonkey spew (JS shell)</h3>
<p>IonMonkey spew is extremely verbose (not as much as the INFER spew), but you can filter it to focus on the list of compiled scripts or channels, IonMonkey spew channels can be selected with the IONFLAGS environment variable, and compilation spew can be filtered with IONFILTER.</p>
<p>IONFLAGS contains the names of each channel separated by commas. The <strong>logs</strong> channel produces 2 files in <em>/tmp/</em>, one (<em>/tmp/ion.json</em>) made to be used with <a class="external text" href="https://github.com/sstangl/iongraph" rel="nofollow">iongraph</a> (made by Sean Stangl) and another one (<em>/tmp/ion.cfg</em>) made to be used with <a class="external text" href="http://java.net/projects/c1visualizer/" rel="nofollow">c1visualizer</a>. These tools will show the MIR & LIR steps done by IonMonkey during the compilation. If you would like to use <a class="external text" href="https://github.com/sstangl/iongraph" rel="nofollow" style="line-height: 1.572;">iongraph</a>, you must install <a href="http://www.graphviz.org/Download..php" title="http://www.graphviz.org/Download..php">Graphviz</a>.</p>
<p>Compilation logs and spew can be filtered with the IONFILTER environment variable which contains locations as output in other spew channels. Multiple locations can be separated with comma as a separator of locations.</p>
<pre class="eval"><code>$ IONFILTER=pdfjs.js:16934 IONFLAGS=logs,scripts,osi,bailouts ./js --ion-offthread-compile=off ./run.js 2>&1 | less</code></pre>
<p>The <strong>bailouts</strong> channel is likely to be the first thing you should focus on, because this means that something does not stay in IonMonkey and fallback to the interpreter. This channel output locations (as returned by the<strong> id()</strong> function of both instructions) of the latest MIR and the latest LIR phases. These locations should correspond to phases of the <strong>logs</strong> and a filter can be used to remove uninteresting functions.</p>
<h3 id="Using_the_ARM_simulator">Using the ARM simulator</h3>
<p>The ARM simulator can be used to test the ARM JIT backend on x86/x64 hardware. An ARM simulator build is an x86 shell (or browser) with the ARM JIT backend. Instead of entering JIT code, it runs it in a simulator (interpreter) for ARM code. To use the simulator, compile an x86 shell (32-bit, x64 doesn't work as we use a different Value format there), and pass --enable-arm-simulator to configure. For instance, on a 64-bit Linux host you can use the following configure command to get an ARM simulator build:</p>
<pre class="line-numbers language-html"><code class="language-html">AR=ar CC="gcc -m32" CXX="g++ -m32" ../configure --target=i686-pc-linux --enable-debug --disable-optimize --enable-threadsafe --enable-simulator=arm</code></pre>
<p>Or on OS X:</p>
<pre> $ AR=ar CC="clang -m32" CXX="clang++ -m32" ../configure --target=i686-apple-darwin10.0.0 --enable-debug --disable-optimize --enable-threadsafe --enable-arm-simulator</pre>
<p>An --enable-debug --enable-optimize build is recommended if you want to run jit-tests or jstests.</p>
<h4 id="Use_the_VIXL_Debugger_in_the_simulator_(arm64)">Use the VIXL Debugger in the simulator (arm64)</h4>
<p>Set a breakpoint (see the comments above about <code>masm.breakpoint()</code>) and run with the environment variable <code>USE_DEBUGGER=1</code>. This will then drop you into a simple debugger provided with VIXL, the ARM simulator technology used for arm64 simulation.</p>
<h4 id="Use_the_Simulator_Debugger_for_arm32">Use the Simulator Debugger for arm32</h4>
<p>The same instructions for arm64 in the preceeding section apply, but the environment variable differs: Use <code>ARM_SIM_DEBUGGER=1</code>.</p>
<h4 id="Building_the_browser_with_the_ARM_simulator">Building the browser with the ARM simulator</h4>
<p>You can also build the entire browser with the ARM simulator backend, for instance to reproduce browser-only JS failures on ARM. Make sure to build a browser for x86 (32-bits) and add this option to your mozconfig file:</p>
<pre>ac_add_options --enable-arm-simulator
</pre>
<p>If you are under an Ubuntu or Debian 64-bits distribution and you want to build a 32-bits browser, it might be hard to find the relevant 32-bits dependencies. You can use <a href="https://github.com/padenot/fx-32-on-64.sh">padenot's scripts</a> which will magically setup a chrooted 32-bits environment and do All The Things (c) for you (you just need to modify the mozconfig file).</p>
<h3 id="Using_rr_on_a_test">Using rr on a test</h3>
<p>Get the command line for your test run using -s:</p>
<pre>./jit_test.py -s $JS_SHELL saved-stacks/async.js</pre>
<p>Insert 'rr' before the shell invocation:</p>
<pre><code>rr $JS_SHELL -f $JS_SRC/jit-test/lib/prolog.js --js-cache $JS_SRC/jit-test/.js-cache -e "const platform='linux2'; const libdir='$JS_SRC/jit-test/lib/'; const scriptdir='$JS_SRC/jit-test/tests/saved-stacks/'" -f $JS_SRC/jit-test/tests/saved-stacks/async.js</code></pre>
<p>(note that the above is an example; simply setting JS_SHELL and JS_SRC will not work). Or if this is an intermittent, run it in a loop capturing an rr log for every one until it fails:</p>
<pre><code>n=1; while rr ...same.as.above...; do echo passed $n; n=$(( $n + 1 )); done</code></pre>
<p>Wait until it hits a failure. Now you can run <code>rr replay</code> to replay that last (failed) run under gdb.</p>
<h4 id="rr_with_reftest">rr with reftest</h4>
<p>To break on the write of a differing pixel:</p>
<ol>
<li>Find the X/Y of a pixel that differs</li>
<li>Use 'run Z' where Z is the mark in the log for TEST-START. For example in '[rr 28496 607198]REFTEST TEST-START | file:///home/bgirard/mozilla-central/tree/image/test/reftest/bmp/bmpsuite/b/wrapper.html?badpalettesize.bmp' Z would be 607198.</li>
<li>break 'mozilla::dom::CanvasRenderingContext2D::DrawWindow(nsGlobalWindow&, double, double, double, double, nsAString_internal const&, unsigned int, mozilla::ErrorResult&)'</li>
<li>cont</li>
<li>break 'PresShell::RenderDocument(nsRect const&, unsigned int, unsigned int, gfxContext*)'</li>
<li>set print object on</li>
<li>set $x = <YOUR X VALUE></li>
<li>set $y = <YOUR Y VALUE></li>
<li>print &((cairo_image_surface_t*)aThebesContext->mDT.mRawPtr->mSurface).data[$y * ((cairo_image_surface_t*)aThebesContext->mDT.mRawPtr->mSurface).stride + $x * ((cairo_image_surface_t*)aThebesContext->mDT.mRawPtr->mSurface).depth / 8]</li>
<li>
<p>watch *(char*)<ADDRESS OF PREVIOUS COMMAND> (NOTE: If you set a watch on the previous expression gdb will watch the expression and run out of watchpoint)</p>
</li>
</ol>
<h4 id="rr_with_emacs">rr with emacs</h4>
<p>Within emacs, do <code>M-x gud-gdb</code> and replace the command line with <code>rr replay</code>. When gdb comes up, enter</p>
<pre><code>set annot 1</code></pre>
<p>to get it to emit file location information so that emacs will pop up the corresponding source. Note that if you <code>reverse-continue</code> over a SIGSEGV and you're using the standard .gdbinit that sets a catchpoint for that signal, you'll get an additional stop at the catchpoint. Just <code>reverse-continue</code> again to continue to your breakpoints or whatever.</p>
<h3 id="Hack_Replacing_one_instruction">[Hack] Replacing one instruction</h3>
<p>To replace one specific instruction, you can use in visit function of each instruction the JSScript <strong>filename</strong> in <strong>lineno</strong> fields as well as the <strong>id()</strong> of the LIR / MIR instructions. The JSScript can be obtained from <strong>info().script()</strong>.</p>
<pre class="eval"><code>bool
CodeGeneratorX86Shared::visitGuardShape(LGuardShape *guard)
{
if (info().script()->lineno == 16934 && guard->id() == 522) {
[… another impl only for this one …]
return true;
}
[… old impl …]
</code></pre>
<h3 id="Hack_Spewing_all_compiled_code">[Hack] Spewing all compiled code</h3>
<p>I usually just add this to the apropriate executableCopy.</p>
<pre><span class="quote"> if (getenv("INST_DUMP")) {
char buf[4096];
sprintf(buf, "gdb /proc/%d/exe %d -batch -ex 'set pagination off' -ex 'set arm force-mode arm' -ex 'x/%di %p' -ex 'set arm force-mode auto'", getpid(), getpid(), m_buffer.size() / 4, buffer);
system(buf);
}</span>
</pre>
<p>If you aren't running on arm, you should omit the -ex 'set arm force-mode arm' and -ex 'set arm force-mode auto'. And you should change the size()/4 to be something more apropriate for your architecture.</p>
<h3 id="Benchmarking_with_sub-milliseconds_(JS_shell)">Benchmarking with sub-milliseconds (JS shell)</h3>
<p>In the shell we have 2 simple ways to benchmark a script, we can either use the <strong>-b</strong> shell option (<strong>--print-timing</strong>) which will evaluate a script given on the command line without any need to instrument the benchmark and print an extra line showing the run-time of the script. The other way is to wrap the section that you want to measure with the <strong>dateNow()</strong> function call which returns the number of milliseconds, with a decimal part for sub-milliseconds.</p>
<pre class="eval">js> dateNow() - dateNow()
-0.0009765625<code>
</code></pre>
<h3 id="Benchmarking_with_sub-milliseconds_(browser)">Benchmarking with sub-milliseconds (browser)</h3>
<p>In a simillar way as <strong>dateNow()</strong> in the JS shell, you can use <strong>performance.now()</strong> in the JavaScript code of a page.</p>
<h3 id="Dumping_the_JavaScript_heap">Dumping the JavaScript heap</h3>
<p>From the shell, you can call the dumpHeap before Firefox function to dump out all GC things (reachable and unreachable) that are present in the heap. By default the function writes to stdout, but a filename can be specified as an argument.</p>
<p>Example output might look as follows:</p>
<pre class="eval"><code>0x1234abcd B global object
</code>==========
# zone 0x56789123
# compartment http://gmail.com [in zone 0x56789123]
# compartment http://gmail.com/iframe [in zone 0x56789123]
# arena<code> allockind=3 size=64
0x1234abcd B object
> 0x1234abcd B prop1
> 0xabcd1234 W prop2
0xabcd1234 W object
> 0xdeadbeef B prop3
# arena allockind=5 size=72
0xdeadbeef W object
> 0xabcd1234 W prop4 </code></pre>
<p>The output is textual. The first section of the file contains a list of roots, one per line. Each root has the form "0xabcd1234 <color> <description>", where <color> is the color of the given GC thing (B for black, G for gray, W for white) and <description> is a string. The list of roots ends with a line containing "==========".</p>
<p>After the roots come a series of zones. A zone starts with several "comment lines" that start with hashes. The first comment declares the zone. It is followed by lines listing each compartment within the zone. After all the compartments come arenas, which is where the GC things are actually stored. Each arena is followed by all the GC things in the arena. A GC thing starts with a line giving its address, its color, and the thing kind (object, function, whatever). After this come a list of addresses that the GC thing points to, each one starting with ">".</p>
<p>It's also possible to dump the JavaScript heap from C++ code (or from gdb) using the js::DumpHeap function. It is part of jsfriendapi.h and it is available in release builds.</p>
<h3 id="Inspecting_MIR_objects_within_a_debugger">Inspecting MIR objects within a debugger</h3>
<p>For MIRGraph, MBasicBlock, and MDefinition and its subclasses (MInstruction, MConstant, etc.), call the dump member function.</p>
<pre> (gdb) call graph->dump()
(gdb) call block->dump()
(gdb) call def->dump()
</pre>
<h3 id="Benchmarking_without_a_Phone">Benchmarking without a Phone</h3>
<p>If you do not have a mobile device or prefer to test on your desktop first, you will need to downgrade your computer such as it is able to run programs as fast as-if they were running on a phone.</p>
<p>On Linux, you can manage the resources available to one program by using cgroup, and to do you can install <strong>libcgroup</strong> which provides some convenient tools such as <strong>cgexec</strong> to wrap the program that you want to benchmark.</p>
<p>The following list of commands is used to create 3 control groups. The top-level control group is just to group the <strong>mask</strong> and the <strong>negate-mask</strong>. The <strong>mask</strong> control group is used to run the program that we want to benchmark. The <strong>negate-mask</strong> control group is used to reserve resources which might be used by the other program if not reserved.</p>
<pre> $ sudo cgcreate -a nicolas:users -t nicolas:users -g cpuset,cpu,memory:/benchmarks
$ cgcreate -a nicolas:users -t nicolas:users -g cpuset,cpu,memory:/benchmarks/mask
$ cgcreate -a nicolas:users -t nicolas:users -g cpuset,cpu,memory:/benchmarks/negate-mask
</pre>
<p>Then we restrict programs of these groups to the first core of the CPU. This is a list of cpu, which means that we can allocate 2 cores by doing <strong>0-1</strong> instead of <strong>0</strong>.</p>
<pre> $ cgset -r cpuset.cpus=0 /benchmarks
$ cgset -r cpuset.cpus=0 /benchmarks/mask
$ cgset -r cpuset.cpus=0 /benchmarks/negate-mask
</pre>
<p>Then we restrict programs of these groups to the first memory node. Most of the time you will only have one, otherwise you should read what is the best setting to set here. If this is not set, you will have some error when you will try to write a pid in <strong>/sys/fs/cgroup/cpuset/benchmarks/mask/tasks</strong> while running <strong>cgexec</strong>.</p>
<pre> $ cgset -r cpuset.mems=0 /benchmarks
$ cgset -r cpuset.mems=0 /benchmarks/mask
$ cgset -r cpuset.mems=0 /benchmarks/negate-mask
</pre>
<p>Then we limit the performance of the CPU, as a proportion such as the result approximately correspond to what you might have if you were running on a phone. For example an Unagi is approximately 40 times slower than my computer. So I allocate <strong>1/40</strong> for the mask, and <strong>39/40</strong> for the negate-mask.</p>
<pre> $ cgset -r cpu.shares=1 /benchmarks/mask
$ cgset -r cpu.shares=39 /benchmarks/negate-mask
</pre>
<p>Then we limit the memory available, to what would be available on the phone. For example an Unagi you want to limit this to 512 MB. As there is no swap, on this device, we set the <strong>memsw</strong> (Memory+Swap) to the same value.</p>
<pre> $ cgset -r memory.limit_in_bytes=$((512*1024*1024)) /benchmarks/mask
$ cgset -r memory.memsw.limit_in_bytes=$((512*1024*1024)) /benchmarks/mask
</pre>
<p>And finally, we run the program that we want to benchmark after the one which is consuming resources. In case of the JS Shell we might also want to set the amount of memory available to change the GC settings as if we were running on a Firefox OS device.</p>
<pre> $ cgexec -g 'cpuset,cpu,memory:/benchmarks/negate-mask' yes > /dev/null &
$ cgexec -g 'cpuset,cpu,memory:/benchmarks/mask' ./js --available-memory=512 ./run.js
</pre>
<h3 id="How_to_debug_oomTest()_failures">How to debug oomTest() failures</h3>
<p>The oomTest() function executes a piece of code many times, simulating an OOM failure at each successive allocation it makes. It's designed to highlight incorrect OOM handling and this may show up as a crash or assertion failure at some later point.</p>
<p>When debugging such a crash the most useful thing is to locate the last simulated alloction failure, as it's usually this that has caused the subsequent crash.</p>
<p>My workflow for doing this is as follows:</p>
<ol>
<li>Build a version of the engine with <code>--enable-debug</code> and <code>--enable-oom-breakpoint</code> configure flags.</li>
<li>Set the environment variable <code>OOM_VERBOSE=1</code> and reproduce the failure. This will print an allocation count at each simulated failure. Note the count of the last allocation.</li>
<li>Run the engine under a debugger and set a breakpoint on the function <code>js_failedAllocBreakpoint</code>.</li>
<li>Run the program and continue the necessary number of times until you reach the final allocation.
<ul>
<li>e.g. in lldb, if the allocation failure number shown is 1500, run `continue -i 1498` (subtracted 2 because we've already hit it once and don't want to skip the last). Drop "-i" for gdb.</li>
</ul>
</li>
<li>Dump a backtrace. This should show you the point at which the OOM is incorrectly handled, which will be a few frames up from the breakpoint.</li>
</ol>
<p>Note: if you are on linux it may be simpler to use rr.</p>
<p>Some guidelines for handling OOM that lead to failures when they are not followed:</p>
<ol>
<li>Check for allocation failure!
<ul>
<li>Fallible allocations should always must be checked and handled, at a minimum by returning a status indicating failure to the caller.</li>
</ul>
</li>
<li>Report OOM to the context if you have one
<ul>
<li>If a function has a <code>JSContext*</code> argument, usually it should call <code>js::ReportOutOfMemory(cx)</code> on allocation failure to report this to the context.</li>
</ul>
</li>
<li>Sometimes it's OK to ignore OOM
<ul>
<li>For example if you are performing a speculative optimisation you might abandon it and continue anyway. But in this case you may have to call cx->recoverFromOutOfMemory() if something further down the stack has already reported the failure.</li>
</ul>
</li>
</ol>
<h3 id="Debugging_GC_markingrooting">Debugging GC marking/rooting</h3>
<p>The <strong>js::debug</strong> namespace contains some functions that are useful for watching mark bits for an individual JSObject* (or any Cell*). <strong>js/src/gc/Heap.h</strong> contains a comment describing an example usage. Reproduced here:</p>
<pre>// Sample usage from gdb:
//
// (gdb) p $word = js::debug::GetMarkWordAddress(obj)
// $1 = (uintptr_t *) 0x7fa56d5fe360
// (gdb) p/x $mask = js::debug::GetMarkMask(obj, js::gc::GRAY)
// $2 = 0x200000000
// (gdb) watch *$word
// Hardware watchpoint 7: *$word
// (gdb) cond 7 *$word & $mask
// (gdb) cont
//
// Note that this is *not* a watchpoint on a single bit. It is a watchpoint on
// the whole word, which will trigger whenever the word changes and the
// selected bit is set after the change.
//
// So if the bit changing is the desired one, this is exactly what you want.
// But if a different bit changes (either set or cleared), you may still stop
// execution if the $mask bit happened to already be set. gdb does not expose
// enough information to restrict the watchpoint to just a single bit.
</pre>
<p>Most of the time, you will want <strong>js::gc::BLACK</strong> (or you can just use 0) for the 2nd param to <strong>js::debug::GetMarkMask</strong>.</p>
|