root/tools/perf/util/srcline.c

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. dso__name
  2. inline_list__append
  3. gnu_basename
  4. srcline_from_fileline
  5. new_inline_sym
  6. bfd_error
  7. slurp_symtab
  8. find_address_in_section
  9. addr2line_init
  10. addr2line_cleanup
  11. inline_list__append_dso_a2l
  12. addr2line
  13. dso__free_a2l
  14. addr2inlines
  15. filename_split
  16. addr2line
  17. dso__free_a2l
  18. addr2inlines
  19. __get_srcline
  20. get_srcline_split
  21. free_srcline
  22. get_srcline
  23. srcline__tree_insert
  24. srcline__tree_find
  25. srcline__tree_delete
  26. dso__parse_addr_inlines
  27. inline_node__delete
  28. inlines__tree_insert
  29. inlines__tree_find
  30. inlines__tree_delete

   1 // SPDX-License-Identifier: GPL-2.0
   2 #include <inttypes.h>
   3 #include <stdio.h>
   4 #include <stdlib.h>
   5 #include <string.h>
   6 
   7 #include <linux/kernel.h>
   8 #include <linux/string.h>
   9 #include <linux/zalloc.h>
  10 
  11 #include "util/dso.h"
  12 #include "util/debug.h"
  13 #include "util/callchain.h"
  14 #include "util/symbol_conf.h"
  15 #include "srcline.h"
  16 #include "string2.h"
  17 #include "symbol.h"
  18 
  19 bool srcline_full_filename;
  20 
  21 static const char *dso__name(struct dso *dso)
  22 {
  23         const char *dso_name;
  24 
  25         if (dso->symsrc_filename)
  26                 dso_name = dso->symsrc_filename;
  27         else
  28                 dso_name = dso->long_name;
  29 
  30         if (dso_name[0] == '[')
  31                 return NULL;
  32 
  33         if (!strncmp(dso_name, "/tmp/perf-", 10))
  34                 return NULL;
  35 
  36         return dso_name;
  37 }
  38 
  39 static int inline_list__append(struct symbol *symbol, char *srcline,
  40                                struct inline_node *node)
  41 {
  42         struct inline_list *ilist;
  43 
  44         ilist = zalloc(sizeof(*ilist));
  45         if (ilist == NULL)
  46                 return -1;
  47 
  48         ilist->symbol = symbol;
  49         ilist->srcline = srcline;
  50 
  51         if (callchain_param.order == ORDER_CALLEE)
  52                 list_add_tail(&ilist->list, &node->val);
  53         else
  54                 list_add(&ilist->list, &node->val);
  55 
  56         return 0;
  57 }
  58 
  59 /* basename version that takes a const input string */
  60 static const char *gnu_basename(const char *path)
  61 {
  62         const char *base = strrchr(path, '/');
  63 
  64         return base ? base + 1 : path;
  65 }
  66 
  67 static char *srcline_from_fileline(const char *file, unsigned int line)
  68 {
  69         char *srcline;
  70 
  71         if (!file)
  72                 return NULL;
  73 
  74         if (!srcline_full_filename)
  75                 file = gnu_basename(file);
  76 
  77         if (asprintf(&srcline, "%s:%u", file, line) < 0)
  78                 return NULL;
  79 
  80         return srcline;
  81 }
  82 
  83 static struct symbol *new_inline_sym(struct dso *dso,
  84                                      struct symbol *base_sym,
  85                                      const char *funcname)
  86 {
  87         struct symbol *inline_sym;
  88         char *demangled = NULL;
  89 
  90         if (!funcname)
  91                 funcname = "??";
  92 
  93         if (dso) {
  94                 demangled = dso__demangle_sym(dso, 0, funcname);
  95                 if (demangled)
  96                         funcname = demangled;
  97         }
  98 
  99         if (base_sym && strcmp(funcname, base_sym->name) == 0) {
 100                 /* reuse the real, existing symbol */
 101                 inline_sym = base_sym;
 102                 /* ensure that we don't alias an inlined symbol, which could
 103                  * lead to double frees in inline_node__delete
 104                  */
 105                 assert(!base_sym->inlined);
 106         } else {
 107                 /* create a fake symbol for the inline frame */
 108                 inline_sym = symbol__new(base_sym ? base_sym->start : 0,
 109                                          base_sym ? (base_sym->end - base_sym->start) : 0,
 110                                          base_sym ? base_sym->binding : 0,
 111                                          base_sym ? base_sym->type : 0,
 112                                          funcname);
 113                 if (inline_sym)
 114                         inline_sym->inlined = 1;
 115         }
 116 
 117         free(demangled);
 118 
 119         return inline_sym;
 120 }
 121 
 122 #ifdef HAVE_LIBBFD_SUPPORT
 123 
 124 /*
 125  * Implement addr2line using libbfd.
 126  */
 127 #define PACKAGE "perf"
 128 #include <bfd.h>
 129 
 130 struct a2l_data {
 131         const char      *input;
 132         u64             addr;
 133 
 134         bool            found;
 135         const char      *filename;
 136         const char      *funcname;
 137         unsigned        line;
 138 
 139         bfd             *abfd;
 140         asymbol         **syms;
 141 };
 142 
 143 static int bfd_error(const char *string)
 144 {
 145         const char *errmsg;
 146 
 147         errmsg = bfd_errmsg(bfd_get_error());
 148         fflush(stdout);
 149 
 150         if (string)
 151                 pr_debug("%s: %s\n", string, errmsg);
 152         else
 153                 pr_debug("%s\n", errmsg);
 154 
 155         return -1;
 156 }
 157 
 158 static int slurp_symtab(bfd *abfd, struct a2l_data *a2l)
 159 {
 160         long storage;
 161         long symcount;
 162         asymbol **syms;
 163         bfd_boolean dynamic = FALSE;
 164 
 165         if ((bfd_get_file_flags(abfd) & HAS_SYMS) == 0)
 166                 return bfd_error(bfd_get_filename(abfd));
 167 
 168         storage = bfd_get_symtab_upper_bound(abfd);
 169         if (storage == 0L) {
 170                 storage = bfd_get_dynamic_symtab_upper_bound(abfd);
 171                 dynamic = TRUE;
 172         }
 173         if (storage < 0L)
 174                 return bfd_error(bfd_get_filename(abfd));
 175 
 176         syms = malloc(storage);
 177         if (dynamic)
 178                 symcount = bfd_canonicalize_dynamic_symtab(abfd, syms);
 179         else
 180                 symcount = bfd_canonicalize_symtab(abfd, syms);
 181 
 182         if (symcount < 0) {
 183                 free(syms);
 184                 return bfd_error(bfd_get_filename(abfd));
 185         }
 186 
 187         a2l->syms = syms;
 188         return 0;
 189 }
 190 
 191 static void find_address_in_section(bfd *abfd, asection *section, void *data)
 192 {
 193         bfd_vma pc, vma;
 194         bfd_size_type size;
 195         struct a2l_data *a2l = data;
 196         flagword flags;
 197 
 198         if (a2l->found)
 199                 return;
 200 
 201 #ifdef bfd_get_section_flags
 202         flags = bfd_get_section_flags(abfd, section);
 203 #else
 204         flags = bfd_section_flags(section);
 205 #endif
 206         if ((flags & SEC_ALLOC) == 0)
 207                 return;
 208 
 209         pc = a2l->addr;
 210 #ifdef bfd_get_section_vma
 211         vma = bfd_get_section_vma(abfd, section);
 212 #else
 213         vma = bfd_section_vma(section);
 214 #endif
 215 #ifdef bfd_get_section_size
 216         size = bfd_get_section_size(section);
 217 #else
 218         size = bfd_section_size(section);
 219 #endif
 220 
 221         if (pc < vma || pc >= vma + size)
 222                 return;
 223 
 224         a2l->found = bfd_find_nearest_line(abfd, section, a2l->syms, pc - vma,
 225                                            &a2l->filename, &a2l->funcname,
 226                                            &a2l->line);
 227 
 228         if (a2l->filename && !strlen(a2l->filename))
 229                 a2l->filename = NULL;
 230 }
 231 
 232 static struct a2l_data *addr2line_init(const char *path)
 233 {
 234         bfd *abfd;
 235         struct a2l_data *a2l = NULL;
 236 
 237         abfd = bfd_openr(path, NULL);
 238         if (abfd == NULL)
 239                 return NULL;
 240 
 241         if (!bfd_check_format(abfd, bfd_object))
 242                 goto out;
 243 
 244         a2l = zalloc(sizeof(*a2l));
 245         if (a2l == NULL)
 246                 goto out;
 247 
 248         a2l->abfd = abfd;
 249         a2l->input = strdup(path);
 250         if (a2l->input == NULL)
 251                 goto out;
 252 
 253         if (slurp_symtab(abfd, a2l))
 254                 goto out;
 255 
 256         return a2l;
 257 
 258 out:
 259         if (a2l) {
 260                 zfree((char **)&a2l->input);
 261                 free(a2l);
 262         }
 263         bfd_close(abfd);
 264         return NULL;
 265 }
 266 
 267 static void addr2line_cleanup(struct a2l_data *a2l)
 268 {
 269         if (a2l->abfd)
 270                 bfd_close(a2l->abfd);
 271         zfree((char **)&a2l->input);
 272         zfree(&a2l->syms);
 273         free(a2l);
 274 }
 275 
 276 #define MAX_INLINE_NEST 1024
 277 
 278 static int inline_list__append_dso_a2l(struct dso *dso,
 279                                        struct inline_node *node,
 280                                        struct symbol *sym)
 281 {
 282         struct a2l_data *a2l = dso->a2l;
 283         struct symbol *inline_sym = new_inline_sym(dso, sym, a2l->funcname);
 284         char *srcline = NULL;
 285 
 286         if (a2l->filename)
 287                 srcline = srcline_from_fileline(a2l->filename, a2l->line);
 288 
 289         return inline_list__append(inline_sym, srcline, node);
 290 }
 291 
 292 static int addr2line(const char *dso_name, u64 addr,
 293                      char **file, unsigned int *line, struct dso *dso,
 294                      bool unwind_inlines, struct inline_node *node,
 295                      struct symbol *sym)
 296 {
 297         int ret = 0;
 298         struct a2l_data *a2l = dso->a2l;
 299 
 300         if (!a2l) {
 301                 dso->a2l = addr2line_init(dso_name);
 302                 a2l = dso->a2l;
 303         }
 304 
 305         if (a2l == NULL) {
 306                 if (!symbol_conf.disable_add2line_warn)
 307                         pr_warning("addr2line_init failed for %s\n", dso_name);
 308                 return 0;
 309         }
 310 
 311         a2l->addr = addr;
 312         a2l->found = false;
 313 
 314         bfd_map_over_sections(a2l->abfd, find_address_in_section, a2l);
 315 
 316         if (!a2l->found)
 317                 return 0;
 318 
 319         if (unwind_inlines) {
 320                 int cnt = 0;
 321 
 322                 if (node && inline_list__append_dso_a2l(dso, node, sym))
 323                         return 0;
 324 
 325                 while (bfd_find_inliner_info(a2l->abfd, &a2l->filename,
 326                                              &a2l->funcname, &a2l->line) &&
 327                        cnt++ < MAX_INLINE_NEST) {
 328 
 329                         if (a2l->filename && !strlen(a2l->filename))
 330                                 a2l->filename = NULL;
 331 
 332                         if (node != NULL) {
 333                                 if (inline_list__append_dso_a2l(dso, node, sym))
 334                                         return 0;
 335                                 // found at least one inline frame
 336                                 ret = 1;
 337                         }
 338                 }
 339         }
 340 
 341         if (file) {
 342                 *file = a2l->filename ? strdup(a2l->filename) : NULL;
 343                 ret = *file ? 1 : 0;
 344         }
 345 
 346         if (line)
 347                 *line = a2l->line;
 348 
 349         return ret;
 350 }
 351 
 352 void dso__free_a2l(struct dso *dso)
 353 {
 354         struct a2l_data *a2l = dso->a2l;
 355 
 356         if (!a2l)
 357                 return;
 358 
 359         addr2line_cleanup(a2l);
 360 
 361         dso->a2l = NULL;
 362 }
 363 
 364 static struct inline_node *addr2inlines(const char *dso_name, u64 addr,
 365                                         struct dso *dso, struct symbol *sym)
 366 {
 367         struct inline_node *node;
 368 
 369         node = zalloc(sizeof(*node));
 370         if (node == NULL) {
 371                 perror("not enough memory for the inline node");
 372                 return NULL;
 373         }
 374 
 375         INIT_LIST_HEAD(&node->val);
 376         node->addr = addr;
 377 
 378         addr2line(dso_name, addr, NULL, NULL, dso, true, node, sym);
 379         return node;
 380 }
 381 
 382 #else /* HAVE_LIBBFD_SUPPORT */
 383 
 384 static int filename_split(char *filename, unsigned int *line_nr)
 385 {
 386         char *sep;
 387 
 388         sep = strchr(filename, '\n');
 389         if (sep)
 390                 *sep = '\0';
 391 
 392         if (!strcmp(filename, "??:0"))
 393                 return 0;
 394 
 395         sep = strchr(filename, ':');
 396         if (sep) {
 397                 *sep++ = '\0';
 398                 *line_nr = strtoul(sep, NULL, 0);
 399                 return 1;
 400         }
 401 
 402         return 0;
 403 }
 404 
 405 static int addr2line(const char *dso_name, u64 addr,
 406                      char **file, unsigned int *line_nr,
 407                      struct dso *dso __maybe_unused,
 408                      bool unwind_inlines __maybe_unused,
 409                      struct inline_node *node __maybe_unused,
 410                      struct symbol *sym __maybe_unused)
 411 {
 412         FILE *fp;
 413         char cmd[PATH_MAX];
 414         char *filename = NULL;
 415         size_t len;
 416         int ret = 0;
 417 
 418         scnprintf(cmd, sizeof(cmd), "addr2line -e %s %016"PRIx64,
 419                   dso_name, addr);
 420 
 421         fp = popen(cmd, "r");
 422         if (fp == NULL) {
 423                 pr_warning("popen failed for %s\n", dso_name);
 424                 return 0;
 425         }
 426 
 427         if (getline(&filename, &len, fp) < 0 || !len) {
 428                 pr_warning("addr2line has no output for %s\n", dso_name);
 429                 goto out;
 430         }
 431 
 432         ret = filename_split(filename, line_nr);
 433         if (ret != 1) {
 434                 free(filename);
 435                 goto out;
 436         }
 437 
 438         *file = filename;
 439 
 440 out:
 441         pclose(fp);
 442         return ret;
 443 }
 444 
 445 void dso__free_a2l(struct dso *dso __maybe_unused)
 446 {
 447 }
 448 
 449 static struct inline_node *addr2inlines(const char *dso_name, u64 addr,
 450                                         struct dso *dso __maybe_unused,
 451                                         struct symbol *sym)
 452 {
 453         FILE *fp;
 454         char cmd[PATH_MAX];
 455         struct inline_node *node;
 456         char *filename = NULL;
 457         char *funcname = NULL;
 458         size_t filelen, funclen;
 459         unsigned int line_nr = 0;
 460 
 461         scnprintf(cmd, sizeof(cmd), "addr2line -e %s -i -f %016"PRIx64,
 462                   dso_name, addr);
 463 
 464         fp = popen(cmd, "r");
 465         if (fp == NULL) {
 466                 pr_err("popen failed for %s\n", dso_name);
 467                 return NULL;
 468         }
 469 
 470         node = zalloc(sizeof(*node));
 471         if (node == NULL) {
 472                 perror("not enough memory for the inline node");
 473                 goto out;
 474         }
 475 
 476         INIT_LIST_HEAD(&node->val);
 477         node->addr = addr;
 478 
 479         /* addr2line -f generates two lines for each inlined functions */
 480         while (getline(&funcname, &funclen, fp) != -1) {
 481                 char *srcline;
 482                 struct symbol *inline_sym;
 483 
 484                 strim(funcname);
 485 
 486                 if (getline(&filename, &filelen, fp) == -1)
 487                         goto out;
 488 
 489                 if (filename_split(filename, &line_nr) != 1)
 490                         goto out;
 491 
 492                 srcline = srcline_from_fileline(filename, line_nr);
 493                 inline_sym = new_inline_sym(dso, sym, funcname);
 494 
 495                 if (inline_list__append(inline_sym, srcline, node) != 0) {
 496                         free(srcline);
 497                         if (inline_sym && inline_sym->inlined)
 498                                 symbol__delete(inline_sym);
 499                         goto out;
 500                 }
 501         }
 502 
 503 out:
 504         pclose(fp);
 505         free(filename);
 506         free(funcname);
 507 
 508         return node;
 509 }
 510 
 511 #endif /* HAVE_LIBBFD_SUPPORT */
 512 
 513 /*
 514  * Number of addr2line failures (without success) before disabling it for that
 515  * dso.
 516  */
 517 #define A2L_FAIL_LIMIT 123
 518 
 519 char *__get_srcline(struct dso *dso, u64 addr, struct symbol *sym,
 520                   bool show_sym, bool show_addr, bool unwind_inlines,
 521                   u64 ip)
 522 {
 523         char *file = NULL;
 524         unsigned line = 0;
 525         char *srcline;
 526         const char *dso_name;
 527 
 528         if (!dso->has_srcline)
 529                 goto out;
 530 
 531         dso_name = dso__name(dso);
 532         if (dso_name == NULL)
 533                 goto out;
 534 
 535         if (!addr2line(dso_name, addr, &file, &line, dso,
 536                        unwind_inlines, NULL, sym))
 537                 goto out;
 538 
 539         srcline = srcline_from_fileline(file, line);
 540         free(file);
 541 
 542         if (!srcline)
 543                 goto out;
 544 
 545         dso->a2l_fails = 0;
 546 
 547         return srcline;
 548 
 549 out:
 550         if (dso->a2l_fails && ++dso->a2l_fails > A2L_FAIL_LIMIT) {
 551                 dso->has_srcline = 0;
 552                 dso__free_a2l(dso);
 553         }
 554 
 555         if (!show_addr)
 556                 return (show_sym && sym) ?
 557                             strndup(sym->name, sym->namelen) : NULL;
 558 
 559         if (sym) {
 560                 if (asprintf(&srcline, "%s+%" PRIu64, show_sym ? sym->name : "",
 561                                         ip - sym->start) < 0)
 562                         return SRCLINE_UNKNOWN;
 563         } else if (asprintf(&srcline, "%s[%" PRIx64 "]", dso->short_name, addr) < 0)
 564                 return SRCLINE_UNKNOWN;
 565         return srcline;
 566 }
 567 
 568 /* Returns filename and fills in line number in line */
 569 char *get_srcline_split(struct dso *dso, u64 addr, unsigned *line)
 570 {
 571         char *file = NULL;
 572         const char *dso_name;
 573 
 574         if (!dso->has_srcline)
 575                 goto out;
 576 
 577         dso_name = dso__name(dso);
 578         if (dso_name == NULL)
 579                 goto out;
 580 
 581         if (!addr2line(dso_name, addr, &file, line, dso, true, NULL, NULL))
 582                 goto out;
 583 
 584         dso->a2l_fails = 0;
 585         return file;
 586 
 587 out:
 588         if (dso->a2l_fails && ++dso->a2l_fails > A2L_FAIL_LIMIT) {
 589                 dso->has_srcline = 0;
 590                 dso__free_a2l(dso);
 591         }
 592 
 593         return NULL;
 594 }
 595 
 596 void free_srcline(char *srcline)
 597 {
 598         if (srcline && strcmp(srcline, SRCLINE_UNKNOWN) != 0)
 599                 free(srcline);
 600 }
 601 
 602 char *get_srcline(struct dso *dso, u64 addr, struct symbol *sym,
 603                   bool show_sym, bool show_addr, u64 ip)
 604 {
 605         return __get_srcline(dso, addr, sym, show_sym, show_addr, false, ip);
 606 }
 607 
 608 struct srcline_node {
 609         u64                     addr;
 610         char                    *srcline;
 611         struct rb_node          rb_node;
 612 };
 613 
 614 void srcline__tree_insert(struct rb_root_cached *tree, u64 addr, char *srcline)
 615 {
 616         struct rb_node **p = &tree->rb_root.rb_node;
 617         struct rb_node *parent = NULL;
 618         struct srcline_node *i, *node;
 619         bool leftmost = true;
 620 
 621         node = zalloc(sizeof(struct srcline_node));
 622         if (!node) {
 623                 perror("not enough memory for the srcline node");
 624                 return;
 625         }
 626 
 627         node->addr = addr;
 628         node->srcline = srcline;
 629 
 630         while (*p != NULL) {
 631                 parent = *p;
 632                 i = rb_entry(parent, struct srcline_node, rb_node);
 633                 if (addr < i->addr)
 634                         p = &(*p)->rb_left;
 635                 else {
 636                         p = &(*p)->rb_right;
 637                         leftmost = false;
 638                 }
 639         }
 640         rb_link_node(&node->rb_node, parent, p);
 641         rb_insert_color_cached(&node->rb_node, tree, leftmost);
 642 }
 643 
 644 char *srcline__tree_find(struct rb_root_cached *tree, u64 addr)
 645 {
 646         struct rb_node *n = tree->rb_root.rb_node;
 647 
 648         while (n) {
 649                 struct srcline_node *i = rb_entry(n, struct srcline_node,
 650                                                   rb_node);
 651 
 652                 if (addr < i->addr)
 653                         n = n->rb_left;
 654                 else if (addr > i->addr)
 655                         n = n->rb_right;
 656                 else
 657                         return i->srcline;
 658         }
 659 
 660         return NULL;
 661 }
 662 
 663 void srcline__tree_delete(struct rb_root_cached *tree)
 664 {
 665         struct srcline_node *pos;
 666         struct rb_node *next = rb_first_cached(tree);
 667 
 668         while (next) {
 669                 pos = rb_entry(next, struct srcline_node, rb_node);
 670                 next = rb_next(&pos->rb_node);
 671                 rb_erase_cached(&pos->rb_node, tree);
 672                 free_srcline(pos->srcline);
 673                 zfree(&pos);
 674         }
 675 }
 676 
 677 struct inline_node *dso__parse_addr_inlines(struct dso *dso, u64 addr,
 678                                             struct symbol *sym)
 679 {
 680         const char *dso_name;
 681 
 682         dso_name = dso__name(dso);
 683         if (dso_name == NULL)
 684                 return NULL;
 685 
 686         return addr2inlines(dso_name, addr, dso, sym);
 687 }
 688 
 689 void inline_node__delete(struct inline_node *node)
 690 {
 691         struct inline_list *ilist, *tmp;
 692 
 693         list_for_each_entry_safe(ilist, tmp, &node->val, list) {
 694                 list_del_init(&ilist->list);
 695                 free_srcline(ilist->srcline);
 696                 /* only the inlined symbols are owned by the list */
 697                 if (ilist->symbol && ilist->symbol->inlined)
 698                         symbol__delete(ilist->symbol);
 699                 free(ilist);
 700         }
 701 
 702         free(node);
 703 }
 704 
 705 void inlines__tree_insert(struct rb_root_cached *tree,
 706                           struct inline_node *inlines)
 707 {
 708         struct rb_node **p = &tree->rb_root.rb_node;
 709         struct rb_node *parent = NULL;
 710         const u64 addr = inlines->addr;
 711         struct inline_node *i;
 712         bool leftmost = true;
 713 
 714         while (*p != NULL) {
 715                 parent = *p;
 716                 i = rb_entry(parent, struct inline_node, rb_node);
 717                 if (addr < i->addr)
 718                         p = &(*p)->rb_left;
 719                 else {
 720                         p = &(*p)->rb_right;
 721                         leftmost = false;
 722                 }
 723         }
 724         rb_link_node(&inlines->rb_node, parent, p);
 725         rb_insert_color_cached(&inlines->rb_node, tree, leftmost);
 726 }
 727 
 728 struct inline_node *inlines__tree_find(struct rb_root_cached *tree, u64 addr)
 729 {
 730         struct rb_node *n = tree->rb_root.rb_node;
 731 
 732         while (n) {
 733                 struct inline_node *i = rb_entry(n, struct inline_node,
 734                                                  rb_node);
 735 
 736                 if (addr < i->addr)
 737                         n = n->rb_left;
 738                 else if (addr > i->addr)
 739                         n = n->rb_right;
 740                 else
 741                         return i;
 742         }
 743 
 744         return NULL;
 745 }
 746 
 747 void inlines__tree_delete(struct rb_root_cached *tree)
 748 {
 749         struct inline_node *pos;
 750         struct rb_node *next = rb_first_cached(tree);
 751 
 752         while (next) {
 753                 pos = rb_entry(next, struct inline_node, rb_node);
 754                 next = rb_next(&pos->rb_node);
 755                 rb_erase_cached(&pos->rb_node, tree);
 756                 inline_node__delete(pos);
 757         }
 758 }

/* [<][>][^][v][top][bottom][index][help] */