annotate.c 18.2 KB
Newer Older
1
#include "../../util/util.h"
2 3 4
#include "../browser.h"
#include "../helpline.h"
#include "../libslang.h"
5 6
#include "../ui.h"
#include "../util.h"
7 8 9 10
#include "../../util/annotate.h"
#include "../../util/hist.h"
#include "../../util/sort.h"
#include "../../util/symbol.h"
11
#include <pthread.h>
12
#include <newt.h>
13

14 15 16 17 18 19 20 21
struct browser_disasm_line {
	struct rb_node	rb_node;
	double		percent;
	u32		idx;
	int		idx_asm;
	bool		jump_target;
};

22 23 24
struct annotate_browser {
	struct ui_browser b;
	struct rb_root	  entries;
25
	struct rb_node	  *curr_hot;
26
	struct disasm_line	  *selection;
27
	struct disasm_line  **offsets;
28
	u64		    start;
29 30 31
	int		    nr_asm_entries;
	int		    nr_entries;
	bool		    hide_src_code;
32
	bool		    use_offset;
33
	bool		    searching_backwards;
34
	u8		    offset_width;
35
	char		    search_bf[128];
36 37
};

38
static inline struct browser_disasm_line *disasm_line__browser(struct disasm_line *dl)
39
{
40
	return (struct browser_disasm_line *)(dl + 1);
41 42
}

43
static bool disasm_line__filter(struct ui_browser *browser, void *entry)
44 45 46 47
{
	struct annotate_browser *ab = container_of(browser, struct annotate_browser, b);

	if (ab->hide_src_code) {
48 49
		struct disasm_line *dl = list_entry(entry, struct disasm_line, node);
		return dl->offset == -1;
50 51 52 53 54
	}

	return false;
}

55 56
static void annotate_browser__write(struct ui_browser *self, void *entry, int row)
{
57
	struct annotate_browser *ab = container_of(self, struct annotate_browser, b);
58
	struct disasm_line *dl = list_entry(entry, struct disasm_line, node);
59
	struct browser_disasm_line *bdl = disasm_line__browser(dl);
60
	bool current_entry = ui_browser__is_current_entry(self, row);
61 62 63
	bool change_color = (!ab->hide_src_code &&
			     (!current_entry || (self->use_navkeypressed &&
					         !self->navkeypressed)));
64 65
	int width = self->width;

66
	if (dl->offset != -1) {
67 68
		ui_browser__set_percent_color(self, bdl->percent, current_entry);
		slsmg_printf(" %7.2f ", bdl->percent);
69
	} else {
70
		ui_browser__set_percent_color(self, 0, current_entry);
71 72 73
		slsmg_write_nstring(" ", 9);
	}

74 75 76 77
	SLsmg_set_char_set(1);
	SLsmg_write_char(SLSMG_VLINE_CHAR);
	SLsmg_set_char_set(0);
	SLsmg_write_char(' ');
78 79 80 81 82

	/* The scroll bar isn't being used */
	if (!self->navkeypressed)
		width += 1;

83
	if (dl->offset != -1 && change_color)
84
		ui_browser__set_color(self, HE_COLORSET_CODE);
85

86
	if (!*dl->line)
87
		slsmg_write_nstring(" ", width - 10);
88
	else if (dl->offset == -1)
89
		slsmg_write_nstring(dl->line, width - 10);
90
	else {
91
		char bf[256];
92
		u64 addr = dl->offset;
93
		int printed, color = -1;
94

95 96 97
		if (!ab->use_offset)
			addr += ab->start;

98 99 100 101 102 103 104 105 106 107 108
		if (!ab->use_offset) {
			printed = scnprintf(bf, sizeof(bf), "%" PRIx64 ":", addr);
		} else {
			if (bdl->jump_target) {
				printed = scnprintf(bf, sizeof(bf), "%*" PRIx64 ":",
						    ab->offset_width, addr);
			} else {
				printed = scnprintf(bf, sizeof(bf), "%*s ",
						    ab->offset_width, " ");
			}
		}
109

110 111 112 113 114
		if (change_color)
			color = ui_browser__set_color(self, HE_COLORSET_ADDR);
		slsmg_write_nstring(bf, printed);
		if (change_color)
			ui_browser__set_color(self, color);
115
		if (dl->ins && dl->ins->ops->scnprintf) {
116 117 118 119 120 121 122 123 124 125 126 127
			if (ins__is_jump(dl->ins)) {
				bool fwd = dl->ops.target > (u64)dl->offset;

				SLsmg_set_char_set(1);
				SLsmg_write_char(fwd ? SLSMG_DARROW_CHAR :
						       SLSMG_UARROW_CHAR);
				SLsmg_set_char_set(0);
				SLsmg_write_char(' ');
			} else {
				slsmg_write_nstring(" ", 2);
			}

128 129
			dl->ins->ops->scnprintf(dl->ins, bf, sizeof(bf), &dl->ops,
						!ab->use_offset);
130 131
			printed += 2;
		} else
132
			scnprintf(bf, sizeof(bf), "  %-6.6s %s", dl->name, dl->ops.raw);
133

134
		slsmg_write_nstring(bf, width - 10 - printed);
135
	}
136

137
	if (current_entry)
138
		ab->selection = dl;
139 140
}

141
static double disasm_line__calc_percent(struct disasm_line *dl, struct symbol *sym, int evidx)
142 143 144
{
	double percent = 0.0;

145
	if (dl->offset != -1) {
146
		int len = sym->end - sym->start;
147
		unsigned int hits = 0;
148
		struct annotation *notes = symbol__annotation(sym);
149
		struct source_line *src_line = notes->src->lines;
150
		struct sym_hist *h = annotation__histogram(notes, evidx);
151 152
		s64 offset = dl->offset;
		struct disasm_line *next;
153

154
		next = disasm__get_next_ip_line(&notes->src->source, dl);
155 156
		while (offset < (s64)len &&
		       (next == NULL || offset < next->offset)) {
157 158
			if (src_line) {
				percent += src_line[offset].percent;
159
			} else
160
				hits += h->addr[offset];
161 162 163

			++offset;
		}
164 165 166 167 168
		/*
 		 * If the percentage wasn't already calculated in
 		 * symbol__get_source_line, do it now:
 		 */
		if (src_line == NULL && h->sum)
169 170 171
			percent = 100.0 * hits / h->sum;
	}

172 173 174
	return percent;
}

175
static void disasm_rb_tree__insert(struct rb_root *root, struct browser_disasm_line *bdl)
176
{
177
	struct rb_node **p = &root->rb_node;
178
	struct rb_node *parent = NULL;
179
	struct browser_disasm_line *l;
180 181 182

	while (*p != NULL) {
		parent = *p;
183 184
		l = rb_entry(parent, struct browser_disasm_line, rb_node);
		if (bdl->percent < l->percent)
185 186 187 188
			p = &(*p)->rb_left;
		else
			p = &(*p)->rb_right;
	}
189 190
	rb_link_node(&bdl->rb_node, parent, p);
	rb_insert_color(&bdl->rb_node, root);
191 192
}

193
static void annotate_browser__set_top(struct annotate_browser *self,
194
				      struct disasm_line *pos, u32 idx)
195 196 197 198 199
{
	unsigned back;

	ui_browser__refresh_dimensions(&self->b);
	back = self->b.height / 2;
200
	self->b.top_idx = self->b.index = idx;
201 202

	while (self->b.top_idx != 0 && back != 0) {
203
		pos = list_entry(pos->node.prev, struct disasm_line, node);
204

205
		if (disasm_line__filter(&self->b, &pos->node))
206 207
			continue;

208 209 210 211 212
		--self->b.top_idx;
		--back;
	}

	self->b.top = pos;
213
	self->b.navkeypressed = true;
214 215 216 217 218
}

static void annotate_browser__set_rb_top(struct annotate_browser *browser,
					 struct rb_node *nd)
{
219
	struct browser_disasm_line *bpos;
220
	struct disasm_line *pos;
221

222 223 224
	bpos = rb_entry(nd, struct browser_disasm_line, rb_node);
	pos = ((struct disasm_line *)bpos) - 1;
	annotate_browser__set_top(browser, pos, bpos->idx);
225
	browser->curr_hot = nd;
226 227
}

228 229
static void annotate_browser__calc_percent(struct annotate_browser *browser,
					   int evidx)
230
{
231 232
	struct map_symbol *ms = browser->b.priv;
	struct symbol *sym = ms->sym;
233
	struct annotation *notes = symbol__annotation(sym);
234
	struct disasm_line *pos;
235 236 237 238 239 240

	browser->entries = RB_ROOT;

	pthread_mutex_lock(&notes->lock);

	list_for_each_entry(pos, &notes->src->source, node) {
241 242 243 244
		struct browser_disasm_line *bpos = disasm_line__browser(pos);
		bpos->percent = disasm_line__calc_percent(pos, sym, evidx);
		if (bpos->percent < 0.01) {
			RB_CLEAR_NODE(&bpos->rb_node);
245 246
			continue;
		}
247
		disasm_rb_tree__insert(&browser->entries, bpos);
248 249 250 251 252 253
	}
	pthread_mutex_unlock(&notes->lock);

	browser->curr_hot = rb_last(&browser->entries);
}

254 255
static bool annotate_browser__toggle_source(struct annotate_browser *browser)
{
256
	struct disasm_line *dl;
257
	struct browser_disasm_line *bdl;
258 259 260
	off_t offset = browser->b.index - browser->b.top_idx;

	browser->b.seek(&browser->b, offset, SEEK_CUR);
261
	dl = list_entry(browser->b.top, struct disasm_line, node);
262
	bdl = disasm_line__browser(dl);
263 264

	if (browser->hide_src_code) {
265 266
		if (bdl->idx_asm < offset)
			offset = bdl->idx;
267 268 269 270

		browser->b.nr_entries = browser->nr_entries;
		browser->hide_src_code = false;
		browser->b.seek(&browser->b, -offset, SEEK_CUR);
271 272
		browser->b.top_idx = bdl->idx - offset;
		browser->b.index = bdl->idx;
273
	} else {
274
		if (bdl->idx_asm < 0) {
275 276 277 278 279
			ui_helpline__puts("Only available for assembly lines.");
			browser->b.seek(&browser->b, -offset, SEEK_CUR);
			return false;
		}

280 281
		if (bdl->idx_asm < offset)
			offset = bdl->idx_asm;
282 283 284 285

		browser->b.nr_entries = browser->nr_asm_entries;
		browser->hide_src_code = true;
		browser->b.seek(&browser->b, -offset, SEEK_CUR);
286 287
		browser->b.top_idx = bdl->idx_asm - offset;
		browser->b.index = bdl->idx_asm;
288 289 290 291 292
	}

	return true;
}

293 294 295 296 297
static bool annotate_browser__callq(struct annotate_browser *browser,
				    int evidx, void (*timer)(void *arg),
				    void *arg, int delay_secs)
{
	struct map_symbol *ms = browser->b.priv;
298
	struct disasm_line *dl = browser->selection;
299 300 301 302 303
	struct symbol *sym = ms->sym;
	struct annotation *notes;
	struct symbol *target;
	u64 ip;

304
	if (!ins__is_call(dl->ins))
305 306
		return false;

307
	ip = ms->map->map_ip(ms->map, dl->ops.target);
308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329
	target = map__find_symbol(ms->map, ip, NULL);
	if (target == NULL) {
		ui_helpline__puts("The called function was not found.");
		return true;
	}

	notes = symbol__annotation(target);
	pthread_mutex_lock(&notes->lock);

	if (notes->src == NULL && symbol__alloc_hist(target) < 0) {
		pthread_mutex_unlock(&notes->lock);
		ui__warning("Not enough memory for annotating '%s' symbol!\n",
			    target->name);
		return true;
	}

	pthread_mutex_unlock(&notes->lock);
	symbol__tui_annotate(target, ms->map, evidx, timer, arg, delay_secs);
	ui_browser__show_title(&browser->b, sym->name);
	return true;
}

330 331 332
static
struct disasm_line *annotate_browser__find_offset(struct annotate_browser *browser,
					  s64 offset, s64 *idx)
333 334 335 336
{
	struct map_symbol *ms = browser->b.priv;
	struct symbol *sym = ms->sym;
	struct annotation *notes = symbol__annotation(sym);
337
	struct disasm_line *pos;
338 339 340 341 342

	*idx = 0;
	list_for_each_entry(pos, &notes->src->source, node) {
		if (pos->offset == offset)
			return pos;
343
		if (!disasm_line__filter(&browser->b, &pos->node))
344 345 346 347 348 349 350 351
			++*idx;
	}

	return NULL;
}

static bool annotate_browser__jump(struct annotate_browser *browser)
{
352
	struct disasm_line *dl = browser->selection;
353
	s64 idx;
354

355
	if (!ins__is_jump(dl->ins))
356 357
		return false;

358
	dl = annotate_browser__find_offset(browser, dl->ops.target, &idx);
359
	if (dl == NULL) {
360 361 362 363
		ui_helpline__puts("Invallid jump offset");
		return true;
	}

364
	annotate_browser__set_top(browser, dl, idx);
365 366 367 368
	
	return true;
}

369 370 371
static
struct disasm_line *annotate_browser__find_string(struct annotate_browser *browser,
					  char *s, s64 *idx)
372 373 374 375
{
	struct map_symbol *ms = browser->b.priv;
	struct symbol *sym = ms->sym;
	struct annotation *notes = symbol__annotation(sym);
376
	struct disasm_line *pos = browser->selection;
377 378 379

	*idx = browser->b.index;
	list_for_each_entry_continue(pos, &notes->src->source, node) {
380
		if (disasm_line__filter(&browser->b, &pos->node))
381 382 383 384 385 386 387 388 389 390 391 392 393
			continue;

		++*idx;

		if (pos->line && strstr(pos->line, s) != NULL)
			return pos;
	}

	return NULL;
}

static bool __annotate_browser__search(struct annotate_browser *browser)
{
394
	struct disasm_line *dl;
395 396
	s64 idx;

397 398
	dl = annotate_browser__find_string(browser, browser->search_bf, &idx);
	if (dl == NULL) {
399 400 401 402
		ui_helpline__puts("String not found!");
		return false;
	}

403
	annotate_browser__set_top(browser, dl, idx);
404 405 406 407
	browser->searching_backwards = false;
	return true;
}

408 409 410
static
struct disasm_line *annotate_browser__find_string_reverse(struct annotate_browser *browser,
						  char *s, s64 *idx)
411 412 413 414
{
	struct map_symbol *ms = browser->b.priv;
	struct symbol *sym = ms->sym;
	struct annotation *notes = symbol__annotation(sym);
415
	struct disasm_line *pos = browser->selection;
416 417 418

	*idx = browser->b.index;
	list_for_each_entry_continue_reverse(pos, &notes->src->source, node) {
419
		if (disasm_line__filter(&browser->b, &pos->node))
420 421 422 423 424 425 426 427 428 429 430 431 432
			continue;

		--*idx;

		if (pos->line && strstr(pos->line, s) != NULL)
			return pos;
	}

	return NULL;
}

static bool __annotate_browser__search_reverse(struct annotate_browser *browser)
{
433
	struct disasm_line *dl;
434 435
	s64 idx;

436 437
	dl = annotate_browser__find_string_reverse(browser, browser->search_bf, &idx);
	if (dl == NULL) {
438 439 440 441
		ui_helpline__puts("String not found!");
		return false;
	}

442
	annotate_browser__set_top(browser, dl, idx);
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
	browser->searching_backwards = true;
	return true;
}

static bool annotate_browser__search_window(struct annotate_browser *browser,
					    int delay_secs)
{
	if (ui_browser__input_window("Search", "String: ", browser->search_bf,
				     "ENTER: OK, ESC: Cancel",
				     delay_secs * 2) != K_ENTER ||
	    !*browser->search_bf)
		return false;

	return true;
}

static bool annotate_browser__search(struct annotate_browser *browser, int delay_secs)
{
	if (annotate_browser__search_window(browser, delay_secs))
		return __annotate_browser__search(browser);

	return false;
}

static bool annotate_browser__continue_search(struct annotate_browser *browser,
					      int delay_secs)
{
	if (!*browser->search_bf)
		return annotate_browser__search(browser, delay_secs);

	return __annotate_browser__search(browser);
}

static bool annotate_browser__search_reverse(struct annotate_browser *browser,
					   int delay_secs)
{
	if (annotate_browser__search_window(browser, delay_secs))
		return __annotate_browser__search_reverse(browser);

	return false;
}

static
bool annotate_browser__continue_search_reverse(struct annotate_browser *browser,
					       int delay_secs)
{
	if (!*browser->search_bf)
		return annotate_browser__search_reverse(browser, delay_secs);

	return __annotate_browser__search_reverse(browser);
}

495
static int annotate_browser__run(struct annotate_browser *self, int evidx,
496
				 void(*timer)(void *arg),
497
				 void *arg, int delay_secs)
498 499
{
	struct rb_node *nd = NULL;
500 501
	struct map_symbol *ms = self->b.priv;
	struct symbol *sym = ms->sym;
502 503
	const char *help = "<-/ESC: Exit, TAB/shift+TAB: Cycle hot lines, "
			   "H: Go to hottest line, ->/ENTER: Line action, "
504
			   "O: Toggle offset view, "
505
			   "S: Toggle source code view";
506
	int key;
507

508
	if (ui_browser__show(&self->b, sym->name, help) < 0)
509
		return -1;
510 511 512

	annotate_browser__calc_percent(self, evidx);

513
	if (self->curr_hot) {
514
		annotate_browser__set_rb_top(self, self->curr_hot);
515 516
		self->b.navkeypressed = false;
	}
517 518

	nd = self->curr_hot;
519

520
	while (1) {
521
		key = ui_browser__run(&self->b, delay_secs);
522

523
		if (delay_secs != 0) {
524 525 526 527 528 529 530 531 532 533
			annotate_browser__calc_percent(self, evidx);
			/*
			 * Current line focus got out of the list of most active
			 * lines, NULL it so that if TAB|UNTAB is pressed, we
			 * move to curr_hot (current hottest line).
			 */
			if (nd != NULL && RB_EMPTY_NODE(nd))
				nd = NULL;
		}

534
		switch (key) {
535
		case K_TIMER:
536 537 538 539
			if (timer != NULL)
				timer(arg);

			if (delay_secs != 0)
540 541
				symbol__annotate_decay_histogram(sym, evidx);
			continue;
542
		case K_TAB:
543 544 545 546 547 548
			if (nd != NULL) {
				nd = rb_prev(nd);
				if (nd == NULL)
					nd = rb_last(&self->entries);
			} else
				nd = self->curr_hot;
549
			break;
550
		case K_UNTAB:
551 552 553 554 555 556 557 558
			if (nd != NULL)
				nd = rb_next(nd);
				if (nd == NULL)
					nd = rb_first(&self->entries);
			else
				nd = self->curr_hot;
			break;
		case 'H':
559
		case 'h':
560
			nd = self->curr_hot;
561
			break;
562
		case 'S':
563
		case 's':
564 565 566
			if (annotate_browser__toggle_source(self))
				ui_helpline__puts(help);
			continue;
567 568 569 570
		case 'O':
		case 'o':
			self->use_offset = !self->use_offset;
			continue;
571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586
		case '/':
			if (annotate_browser__search(self, delay_secs)) {
show_help:
				ui_helpline__puts(help);
			}
			continue;
		case 'n':
			if (self->searching_backwards ?
			    annotate_browser__continue_search_reverse(self, delay_secs) :
			    annotate_browser__continue_search(self, delay_secs))
				goto show_help;
			continue;
		case '?':
			if (annotate_browser__search_reverse(self, delay_secs))
				goto show_help;
			continue;
587 588
		case K_ENTER:
		case K_RIGHT:
589
			if (self->selection == NULL)
590
				ui_helpline__puts("Huh? No selection. Report to linux-kernel@vger.kernel.org");
591
			else if (self->selection->offset == -1)
592
				ui_helpline__puts("Actions are only available for assembly lines.");
593 594
			else if (!self->selection->ins ||
				 !(annotate_browser__jump(self) ||
595 596
				   annotate_browser__callq(self, evidx, timer, arg, delay_secs)))
				ui_helpline__puts("Actions are only available for the 'callq' and jump instructions.");
597
			continue;
598 599
		case K_LEFT:
		case K_ESC:
600 601
		case 'q':
		case CTRL('c'):
602
			goto out;
603 604
		default:
			continue;
605
		}
606 607

		if (nd != NULL)
608
			annotate_browser__set_rb_top(self, nd);
609 610
	}
out:
611
	ui_browser__hide(&self->b);
612
	return key;
613 614
}

615
int hist_entry__tui_annotate(struct hist_entry *he, int evidx,
616
			     void(*timer)(void *arg), void *arg, int delay_secs)
617
{
618
	return symbol__tui_annotate(he->ms.sym, he->ms.map, evidx,
619
				    timer, arg, delay_secs);
620 621
}

622 623 624 625 626 627 628 629 630 631 632 633
static void annotate_browser__mark_jump_targets(struct annotate_browser *browser,
						size_t size)
{
	u64 offset;

	for (offset = 0; offset < size; ++offset) {
		struct disasm_line *dl = browser->offsets[offset], *dlt;
		struct browser_disasm_line *bdlt;

		if (!dl || !dl->ins || !ins__is_jump(dl->ins))
			continue;

634
		if (dl->ops.target >= size) {
635 636
			ui__error("jump to after symbol!\n"
				  "size: %zx, jump target: %" PRIx64,
637
				  size, dl->ops.target);
638 639 640
			continue;
		}

641
		dlt = browser->offsets[dl->ops.target];
642 643 644 645 646 647
		bdlt = disasm_line__browser(dlt);
		bdlt->jump_target = true;
	}
		
}

648
int symbol__tui_annotate(struct symbol *sym, struct map *map, int evidx,
649
			 void(*timer)(void *arg), void *arg,
650
			 int delay_secs)
651
{
652
	struct disasm_line *pos, *n;
653
	struct annotation *notes;
654
	const size_t size = symbol__size(sym);
655 656 657 658
	struct map_symbol ms = {
		.map = map,
		.sym = sym,
	};
659 660 661 662 663
	struct annotate_browser browser = {
		.b = {
			.refresh = ui_browser__list_head_refresh,
			.seek	 = ui_browser__list_head_seek,
			.write	 = annotate_browser__write,
664
			.filter  = disasm_line__filter,
665
			.priv	 = &ms,
666
			.use_navkeypressed = true,
667
		},
668
		.use_offset = true,
669
	};
670
	int ret = -1;
671

672
	if (sym == NULL)
673 674
		return -1;

675
	if (map->dso->annotate_warned)
676 677
		return -1;

678 679 680 681 682 683
	browser.offsets = zalloc(size * sizeof(struct disasm_line *));
	if (browser.offsets == NULL) {
		ui__error("Not enough memory!");
		return -1;
	}

684
	if (symbol__annotate(sym, map, sizeof(struct browser_disasm_line)) < 0) {
685
		ui__error("%s", ui_helpline__last_msg);
686
		goto out_free_offsets;
687 688 689 690
	}

	ui_helpline__push("Press <- or ESC to exit");

691
	notes = symbol__annotation(sym);
692
	browser.start = map__rip_2objdump(map, sym->start);
693

694
	list_for_each_entry(pos, &notes->src->source, node) {
695
		struct browser_disasm_line *bpos;
696
		size_t line_len = strlen(pos->line);
697

698 699
		if (browser.b.width < line_len)
			browser.b.width = line_len;
700 701
		bpos = disasm_line__browser(pos);
		bpos->idx = browser.nr_entries++;
702
		if (pos->offset != -1) {
703
			bpos->idx_asm = browser.nr_asm_entries++;
704 705 706 707 708 709 710 711 712
			/*
			 * FIXME: short term bandaid to cope with assembly
			 * routines that comes with labels in the same column
			 * as the address in objdump, sigh.
			 *
			 * E.g. copy_user_generic_unrolled
 			 */
			if (pos->offset < (s64)size)
				browser.offsets[pos->offset] = pos;
713
		} else
714
			bpos->idx_asm = -1;
715 716
	}

717 718
	annotate_browser__mark_jump_targets(&browser, size);

719
	browser.offset_width = hex_width(size);
720
	browser.b.nr_entries = browser.nr_entries;
721
	browser.b.entries = &notes->src->source,
722
	browser.b.width += 18; /* Percentage */
723
	ret = annotate_browser__run(&browser, evidx, timer, arg, delay_secs);
724
	list_for_each_entry_safe(pos, n, &notes->src->source, node) {
725
		list_del(&pos->node);
726
		disasm_line__free(pos);
727
	}
728 729 730

out_free_offsets:
	free(browser.offsets);
731 732
	return ret;
}