⑨ lab ≡ ByteLabs

Plan9/Debugging with Acid

— Igor Böhm

Random notes on how to debug Plan9 programs using acid

Debugging acme in acme using acid…

Debugging acme in acme using acid

Overview

There is a rendering glitch in acme. Click on the image above to see a larger version and take a look at the bottom part of the large acme window (can you spot it?)…

After some random trawling through the code it seems like the acme function /sys/src/cmd/acme/cols.c:/^colgrow might be responsible for the glitch. The function is a bit too complex to digest by simply reading the code so we are going to use acid to debug it, and hope to find and fix the issue, or at least better understand the logic.

You can also watch the debug session as a youtube screencast.

Symbols

To enable examination of complex types and structs, acid requires additional information. For base 9front applications and commands this information can easily be generated via the mk target syms or by mk acme.acid:

 % cd /sys/src/cmd/acme
 % mk syms
6c -a acme.c > syms
for(i in ????.c) 6c -aa $i >> syms
 …
 % mk acme.acid
6c -FTVw acme.c
6c -FTVw -a acme.c >acme.acid

This generates complex type and structure definitions in the file /sys/src/cmd/acme/(syms|acme.acid) for acid so we can view any struct we like. Take a look at your compiler frontend options (i.e. options -na in 2c(2)) that describe how to generate this information.

Start Acme

To run acme execute the following:

 % window acme

…and determine its PID. This instance of acme will be the one we are going to debug.

Attach Acid

To attach acid to the running acme instance invoke:

 % acid -l /sys/src/cmd/acme/syms 117580
/proc/117580/text:amd64 plan 9 executable
/sys/lib/acid/port
/sys/lib/acid/amd64
acid:

At this point acid has attached to the program we are interested in and the debugging can start (NOTE the acid: prompt).

Set Breakpoints

At the acid: prompt we are going to set a breakpoint on the colgrow() function using the command bpset(colgrow):

acid: bpset(colgrow)
Waiting...
117580: system call	rendezvous+0xe	RET
acid:

Let’s make sure the breakpoint was set correctly by listing all set breakpoints using bptab():

acid: bptab()
	0x0000000000205944 colgrow  SUBQ	$0xa8,SP

To ensure the breakpoint is correct it helps to print the statement with some context source code around it (NOTE how the breakpoint address printed using bptab() is used as a parameter to src()):

acid: src(0x0000000000205944)
/sys/src/cmd/acme/cols.c:282
 277		free(c->w);
 278		c->w = wp;
 279	}
 280	
 281	void
>282	colgrow(Column *c, Window *w, int but)
 283	{
 284		Rectangle r, cr;
 285		int i, j, k, l, y1, y2, *nl, *ny, tot, nnl, onl, dnl, h;
 286		Window *v;
 287	
acid: 

That looks Ok. Let’s cont()inue and actually trigger the breakpoint by resizing a window in acme:

acid: cont()
117580: breakpoint	colgrow	SUBQ	$0xa8,SP

Let’s examine where in the source we are:

acid: src(*PC)
/sys/src/cmd/acme/cols.c:282
 277		free(c->w);
 278		c->w = wp;
 279	}
 280	
 281	void
>282	colgrow(Column *c, Window *w, int but)
 283	{
 284		Rectangle r, cr;
 285		int i, j, k, l, y1, y2, *nl, *ny, tot, nnl, onl, dnl, h;
 286		Window *v;
 287	

The breakpoint set to the function entry has triggered.

Step and Inspect

Great! We are where we are supposed to be. Now let’s step over a few statements using next() and start examining some data structures:

acid: next()
117580: breakpoint	colgrow+0x7	MOVQ	w+0x8(FP),BX
117580: breakpoint	colgrow+0xf	MOVQ	BP,CX
117580: breakpoint	colgrow+0x12	XORL	DX,DX
/sys/src/cmd/acme/cols.c:288
 283	{
 284		Rectangle r, cr;
 285		int i, j, k, l, y1, y2, *nl, *ny, tot, nnl, onl, dnl, h;
 286		Window *v;
 287	
>288		for(i=0; i<c->nw; i++)
 289			if(c->w[i] == w)
 290				goto Found;
 291		error("can't find window");
 292	
 293	  Found:
acid: next()
...
acid: next()
117580: breakpoint	colgrow+0xcd4	JMP	colgrow+0x14(SB)
117580: breakpoint	colgrow+0x14	MOVQ	CX,c+0x0(FP)
117580: breakpoint	colgrow+0x1c	MOVL	0x150(CX),DI
117580: breakpoint	colgrow+0x22	CMPL	DI,DX
117580: breakpoint	colgrow+0x24	MOVL	DX,i+0x84(SP)
117580: breakpoint	colgrow+0x2b	JGE	colgrow+0xcd9(SB)
117580: breakpoint	colgrow+0x31	MOVLQSX	DX,DI
/sys/src/cmd/acme/cols.c:289
 284		Rectangle r, cr;
 285		int i, j, k, l, y1, y2, *nl, *ny, tot, nnl, onl, dnl, h;
 286		Window *v;
 287	
 288		for(i=0; i<c->nw; i++)
>289			if(c->w[i] == w)
 290				goto Found;
 291		error("can't find window");
 292	
 293	  Found:
 294		cr = c->r;
acid: next()
117580: breakpoint	colgrow+0x34	MOVQ	0x148(CX),SI
117580: breakpoint	colgrow+0x3b	MOVQ	0x0(SI)(DI*8),DI
117580: breakpoint	colgrow+0x3f	CMPQ	BX,DI
117580: breakpoint	colgrow+0x42	JNE	colgrow+0xcd2(SB)
117580: breakpoint	colgrow+0x48	MOVQ	0x0(CX),DI
/sys/src/cmd/acme/cols.c:294
 289			if(c->w[i] == w)
 290				goto Found;
 291		error("can't find window");
 292	
 293	  Found:
>294		cr = c->r;
 295		if(but < 0){	/* make sure window fills its own space properly */
 296			r = w->r;
 297			if(i==c->nw-1 || c->safe==FALSE)
 298				r.max.y = cr.max.y;
 299			else

Although the acid function next() prints some context after stepping each stement, it would be good to see more. To quickly open the current location in an editor simply run:

acid: Bsrc(*PC)

Now let’s examine the variable cr. In our editor we see that cr is of type Rectangle. To explore the value of that structure we step using next() and then query the complex type Rectangle using (Rectangle)colgrow:cr:

acid: next()
117580: breakpoint	colgrow+0x4b	MOVQ	DI,cr+0x88(SP)
117580: breakpoint	colgrow+0x53	MOVQ	0x8(CX),DI
117580: breakpoint	colgrow+0x57	MOVQ	DI,0x90(SP)
117580: breakpoint	colgrow+0x5f	CMPL	but+0x10(FP),$0x0
/sys/src/cmd/acme/cols.c:295
 290				goto Found;
 291		error("can't find window");
 292	
 293	  Found:
 294		cr = c->r;
>295		if(but < 0){	/* make sure window fills its own space properly */
 296			r = w->r;
 297			if(i==c->nw-1 || c->safe==FALSE)
 298				r.max.y = cr.max.y;
 299			else
 300				r.max.y = c->w[i+1]->r.min.y;
acid: (Rectangle)colgrow:cr
Point min {
	x	2807
	y	320
}
Point max {
	x	3114
	y	927
}

Note the reference to the variable cr is prefixed with the function name (i.e. its scope).

To understand what is going on towards the end of colgrow() we set another breakpoint at the appropriate line of code and cont() to that line (i.e. one can determine the current line via = in sam or Edit = in acme):

acid: bpset(filepc("/sys/src/cmd/acme/cols.c:430"))
acid: bptab()
	0x0000000000205944 colgrow  SUBQ	$0xa8,SP
	0x00000000002061c1 colgrow+0x87d  MOVQ	w+0x8(FP),SI
acid: cont()
117580: breakpoint	colgrow+0x87d	MOVQ	w+0x8(FP),SI
acid: src(*PC)
/sys/src/cmd/acme/cols.c:430
 425				r.max.y = r.min.y + Border;
 426				draw(screen, r, display->black, nil, ZP);
 427			}
 428			y1 = r.max.y;
 429		}
>430		r = w->r;
 431		r.min.y = y1;
 432		r.max.y = c->r.max.y;
 433		draw(screen, r, textcols[BACK], nil, ZP);
 434		free(nl);
 435		free(ny);

At this stage all that is left to do is to actually find the cause for the rendering issue and fix it, see:

Screencast

Here is the above debug session as a screencast on youtube to give you a better idea of how this works in action:

Summary

Acid is the Plan9 debugger. Once you wrap your head around its basic concepts debugging even complex applications such as acme is not difficult. The above is a simple debug session example and only scratches the surface of what one can do with acid. More advanced features are shown in Russ Cox presentation about acid.

Appendix

The following snippet is useful to have in an acme tag to enable faster debugging via middle clicking and chording:

---
RUN: (acid -l ACIDFILE PID)
BRK: (bpset(FUNNAME)) | (bpset(filepc("src:line"))) | (bptab())
SRC: (Bsrc(*PC))      | (src(*PC))
STP: (cont())         | (next())                    | (run())
SEE: ((TYPE)FUN:VAR)  ((*FUN:VAR)\D)
---

The reason commands are wrapped in parentheses is to speed up selection by double clicking right after ( or before ).

#Plan9 #Dev #9front