addr2line.c revision 289074
1260684Skaiw/*- 2260684Skaiw * Copyright (c) 2009 Kai Wang 3260684Skaiw * All rights reserved. 4260684Skaiw * 5260684Skaiw * Redistribution and use in source and binary forms, with or without 6260684Skaiw * modification, are permitted provided that the following conditions 7260684Skaiw * are met: 8260684Skaiw * 1. Redistributions of source code must retain the above copyright 9260684Skaiw * notice, this list of conditions and the following disclaimer. 10260684Skaiw * 2. Redistributions in binary form must reproduce the above copyright 11260684Skaiw * notice, this list of conditions and the following disclaimer in the 12260684Skaiw * documentation and/or other materials provided with the distribution. 13260684Skaiw * 14260684Skaiw * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND 15260684Skaiw * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16260684Skaiw * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 17260684Skaiw * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE 18260684Skaiw * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19260684Skaiw * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 20260684Skaiw * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 21260684Skaiw * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT 22260684Skaiw * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY 23260684Skaiw * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 24260684Skaiw * SUCH DAMAGE. 25260684Skaiw */ 26260684Skaiw 27260684Skaiw#include <sys/param.h> 28260684Skaiw#include <dwarf.h> 29260684Skaiw#include <err.h> 30260684Skaiw#include <fcntl.h> 31260684Skaiw#include <gelf.h> 32260684Skaiw#include <getopt.h> 33260684Skaiw#include <libdwarf.h> 34260684Skaiw#include <libelftc.h> 35260684Skaiw#include <libgen.h> 36260684Skaiw#include <stdio.h> 37260684Skaiw#include <stdlib.h> 38260684Skaiw#include <string.h> 39260684Skaiw 40260684Skaiw#include "_elftc.h" 41260684Skaiw 42289071SemasteELFTC_VCSID("$Id: addr2line.c 3249 2015-10-04 08:11:30Z kaiwang27 $"); 43260684Skaiw 44260684Skaiwstatic struct option longopts[] = { 45260684Skaiw {"target" , required_argument, NULL, 'b'}, 46260684Skaiw {"demangle", no_argument, NULL, 'C'}, 47260684Skaiw {"exe", required_argument, NULL, 'e'}, 48260684Skaiw {"functions", no_argument, NULL, 'f'}, 49260684Skaiw {"section", required_argument, NULL, 'j'}, 50260684Skaiw {"basename", no_argument, NULL, 's'}, 51260684Skaiw {"help", no_argument, NULL, 'H'}, 52260684Skaiw {"version", no_argument, NULL, 'V'}, 53260684Skaiw {NULL, 0, NULL, 0} 54260684Skaiw}; 55260684Skaiwstatic int demangle, func, base; 56260684Skaiwstatic char unknown[] = { '?', '?', '\0' }; 57260684Skaiwstatic Dwarf_Addr section_base; 58260684Skaiw 59260684Skaiw#define USAGE_MESSAGE "\ 60260684SkaiwUsage: %s [options] hexaddress...\n\ 61260684Skaiw Map program addresses to source file names and line numbers.\n\n\ 62260684Skaiw Options:\n\ 63260684Skaiw -b TGT | --target=TGT (Accepted but ignored).\n\ 64289071Semaste -e EXE | --exe=EXE Use program \"EXE\" to translate addresses.\n\ 65260684Skaiw -f | --functions Display function names.\n\ 66260684Skaiw -j NAME | --section=NAME Values are offsets into section \"NAME\".\n\ 67260684Skaiw -s | --basename Only show the base name for each file name.\n\ 68260684Skaiw -C | --demangle Demangle C++ names.\n\ 69260684Skaiw -H | --help Print a help message.\n\ 70260684Skaiw -V | --version Print a version identifier and exit.\n" 71260684Skaiw 72260684Skaiwstatic void 73260684Skaiwusage(void) 74260684Skaiw{ 75260684Skaiw (void) fprintf(stderr, USAGE_MESSAGE, ELFTC_GETPROGNAME()); 76260684Skaiw exit(1); 77260684Skaiw} 78260684Skaiw 79260684Skaiwstatic void 80260684Skaiwversion(void) 81260684Skaiw{ 82260684Skaiw 83260684Skaiw fprintf(stderr, "%s (%s)\n", ELFTC_GETPROGNAME(), elftc_version()); 84260684Skaiw exit(0); 85260684Skaiw} 86260684Skaiw 87282918Semaste/* 88282918Semaste * Handle DWARF 4 'offset from' DW_AT_high_pc. Although we don't 89282918Semaste * fully support DWARF 4, some compilers (like FreeBSD Clang 3.5.1) 90282918Semaste * generate DW_AT_high_pc as an offset from DW_AT_low_pc. 91282918Semaste * 92282918Semaste * "If the value of the DW_AT_high_pc is of class address, it is the 93282918Semaste * relocated address of the first location past the last instruction 94282918Semaste * associated with the entity; if it is of class constant, the value 95282918Semaste * is an unsigned integer offset which when added to the low PC gives 96282918Semaste * the address of the first location past the last instruction 97282918Semaste * associated with the entity." 98282918Semaste * 99282918Semaste * DWARF4 spec, section 2.17.2. 100282918Semaste */ 101282918Semastestatic int 102282918Semastehandle_high_pc(Dwarf_Die die, Dwarf_Unsigned lopc, Dwarf_Unsigned *hipc) 103282918Semaste{ 104282918Semaste Dwarf_Error de; 105282918Semaste Dwarf_Half form; 106282918Semaste Dwarf_Attribute at; 107282918Semaste int ret; 108282918Semaste 109282918Semaste ret = dwarf_attr(die, DW_AT_high_pc, &at, &de); 110282918Semaste if (ret == DW_DLV_ERROR) { 111282918Semaste warnx("dwarf_attr failed: %s", dwarf_errmsg(de)); 112282918Semaste return (ret); 113282918Semaste } 114282918Semaste ret = dwarf_whatform(at, &form, &de); 115282918Semaste if (ret == DW_DLV_ERROR) { 116282918Semaste warnx("dwarf_whatform failed: %s", dwarf_errmsg(de)); 117282918Semaste return (ret); 118282918Semaste } 119282918Semaste if (dwarf_get_form_class(2, 0, 0, form) == DW_FORM_CLASS_CONSTANT) 120282918Semaste *hipc += lopc; 121282918Semaste 122282918Semaste return (DW_DLV_OK); 123282918Semaste} 124282918Semaste 125260684Skaiwstatic void 126289071Semastesearch_func(Dwarf_Debug dbg, Dwarf_Die die, Dwarf_Addr addr, char **rlt_func) 127260684Skaiw{ 128260684Skaiw Dwarf_Die ret_die, spec_die; 129260684Skaiw Dwarf_Error de; 130260684Skaiw Dwarf_Half tag; 131260684Skaiw Dwarf_Unsigned lopc, hipc; 132260684Skaiw Dwarf_Off ref; 133260684Skaiw Dwarf_Attribute sub_at, spec_at; 134260684Skaiw char *func0; 135289071Semaste const char *func1; 136260684Skaiw int ret; 137260684Skaiw 138260684Skaiw if (*rlt_func != NULL) 139289071Semaste goto done; 140260684Skaiw 141260684Skaiw if (dwarf_tag(die, &tag, &de)) { 142260684Skaiw warnx("dwarf_tag: %s", dwarf_errmsg(de)); 143260684Skaiw goto cont_search; 144260684Skaiw } 145260684Skaiw if (tag == DW_TAG_subprogram) { 146260684Skaiw if (dwarf_attrval_unsigned(die, DW_AT_low_pc, &lopc, &de) || 147260684Skaiw dwarf_attrval_unsigned(die, DW_AT_high_pc, &hipc, &de)) 148260684Skaiw goto cont_search; 149282918Semaste if (handle_high_pc(die, lopc, &hipc) != DW_DLV_OK) 150282918Semaste goto cont_search; 151260684Skaiw if (addr < lopc || addr >= hipc) 152260684Skaiw goto cont_search; 153260684Skaiw 154260684Skaiw /* Found it! */ 155260684Skaiw 156289071Semaste if ((*rlt_func = strdup(unknown)) == NULL) 157289071Semaste err(EXIT_FAILURE, "strdup"); 158260684Skaiw ret = dwarf_attr(die, DW_AT_name, &sub_at, &de); 159260684Skaiw if (ret == DW_DLV_ERROR) 160289071Semaste goto done; 161260684Skaiw if (ret == DW_DLV_OK) { 162289071Semaste if (dwarf_formstring(sub_at, &func0, &de) == 163289071Semaste DW_DLV_OK) { 164289071Semaste free(*rlt_func); 165289071Semaste if ((*rlt_func = strdup(func0)) == NULL) 166289071Semaste err(EXIT_FAILURE, "strdup"); 167289071Semaste } 168289071Semaste goto done; 169260684Skaiw } 170260684Skaiw 171260684Skaiw /* 172260684Skaiw * If DW_AT_name is not present, but DW_AT_specification is 173260684Skaiw * present, then probably the actual name is in the DIE 174260684Skaiw * referenced by DW_AT_specification. 175260684Skaiw */ 176260684Skaiw if (dwarf_attr(die, DW_AT_specification, &spec_at, &de)) 177289071Semaste goto done; 178260684Skaiw if (dwarf_global_formref(spec_at, &ref, &de)) 179289071Semaste goto done; 180260684Skaiw if (dwarf_offdie(dbg, ref, &spec_die, &de)) 181289071Semaste goto done; 182289071Semaste if (dwarf_attrval_string(spec_die, DW_AT_name, &func1, &de) == 183289071Semaste DW_DLV_OK) { 184289071Semaste free(*rlt_func); 185289071Semaste if ((*rlt_func = strdup(func1)) == NULL) 186289071Semaste err(EXIT_FAILURE, "strdup"); 187289071Semaste } 188260684Skaiw 189289071Semaste goto done; 190260684Skaiw } 191260684Skaiw 192260684Skaiwcont_search: 193260684Skaiw 194260684Skaiw /* Search children. */ 195260684Skaiw ret = dwarf_child(die, &ret_die, &de); 196260684Skaiw if (ret == DW_DLV_ERROR) 197260684Skaiw errx(EXIT_FAILURE, "dwarf_child: %s", dwarf_errmsg(de)); 198260684Skaiw else if (ret == DW_DLV_OK) 199260684Skaiw search_func(dbg, ret_die, addr, rlt_func); 200260684Skaiw 201260684Skaiw /* Search sibling. */ 202260684Skaiw ret = dwarf_siblingof(dbg, die, &ret_die, &de); 203260684Skaiw if (ret == DW_DLV_ERROR) 204260684Skaiw errx(EXIT_FAILURE, "dwarf_siblingof: %s", dwarf_errmsg(de)); 205260684Skaiw else if (ret == DW_DLV_OK) 206260684Skaiw search_func(dbg, ret_die, addr, rlt_func); 207289071Semaste 208289071Semastedone: 209289071Semaste dwarf_dealloc(dbg, die, DW_DLA_DIE); 210260684Skaiw} 211260684Skaiw 212260684Skaiwstatic void 213260684Skaiwtranslate(Dwarf_Debug dbg, const char* addrstr) 214260684Skaiw{ 215289071Semaste Dwarf_Die die, ret_die; 216260684Skaiw Dwarf_Line *lbuf; 217260684Skaiw Dwarf_Error de; 218260684Skaiw Dwarf_Half tag; 219260684Skaiw Dwarf_Unsigned lopc, hipc, addr, lineno, plineno; 220260684Skaiw Dwarf_Signed lcount; 221260684Skaiw Dwarf_Addr lineaddr, plineaddr; 222289071Semaste char *funcname; 223260684Skaiw char *file, *file0, *pfile; 224260684Skaiw char demangled[1024]; 225260684Skaiw int i, ret; 226260684Skaiw 227260684Skaiw addr = strtoull(addrstr, NULL, 16); 228260684Skaiw addr += section_base; 229260684Skaiw lineno = 0; 230260684Skaiw file = unknown; 231289074Semaste die = NULL; 232289071Semaste lbuf = NULL; 233289071Semaste lcount = 0; 234260684Skaiw 235260684Skaiw while ((ret = dwarf_next_cu_header(dbg, NULL, NULL, NULL, NULL, NULL, 236260684Skaiw &de)) == DW_DLV_OK) { 237260684Skaiw die = NULL; 238289071Semaste while (dwarf_siblingof(dbg, die, &ret_die, &de) == DW_DLV_OK) { 239289071Semaste if (die != NULL) 240289071Semaste dwarf_dealloc(dbg, die, DW_DLA_DIE); 241289071Semaste die = ret_die; 242260684Skaiw if (dwarf_tag(die, &tag, &de) != DW_DLV_OK) { 243260684Skaiw warnx("dwarf_tag failed: %s", 244260684Skaiw dwarf_errmsg(de)); 245289071Semaste goto next_cu; 246260684Skaiw } 247289071Semaste 248260684Skaiw /* XXX: What about DW_TAG_partial_unit? */ 249260684Skaiw if (tag == DW_TAG_compile_unit) 250260684Skaiw break; 251260684Skaiw } 252289071Semaste if (ret_die == NULL) { 253260684Skaiw warnx("could not find DW_TAG_compile_unit die"); 254289071Semaste goto next_cu; 255260684Skaiw } 256260684Skaiw if (!dwarf_attrval_unsigned(die, DW_AT_low_pc, &lopc, &de) && 257260684Skaiw !dwarf_attrval_unsigned(die, DW_AT_high_pc, &hipc, &de)) { 258260684Skaiw /* 259260684Skaiw * Check if the address falls into the PC range of 260260684Skaiw * this CU. 261260684Skaiw */ 262282918Semaste if (handle_high_pc(die, lopc, &hipc) != DW_DLV_OK) 263289071Semaste goto next_cu; 264260684Skaiw if (addr < lopc || addr >= hipc) 265289071Semaste goto next_cu; 266260684Skaiw } 267260684Skaiw 268288119Semaste switch (dwarf_srclines(die, &lbuf, &lcount, &de)) { 269288119Semaste case DW_DLV_OK: 270288119Semaste break; 271288119Semaste case DW_DLV_NO_ENTRY: 272289071Semaste /* If a CU lacks debug info, just skip it. */ 273289071Semaste goto next_cu; 274288119Semaste default: 275260684Skaiw warnx("dwarf_srclines: %s", dwarf_errmsg(de)); 276260684Skaiw goto out; 277260684Skaiw } 278260684Skaiw 279260684Skaiw plineaddr = ~0ULL; 280260684Skaiw plineno = 0; 281260684Skaiw pfile = unknown; 282260684Skaiw for (i = 0; i < lcount; i++) { 283260684Skaiw if (dwarf_lineaddr(lbuf[i], &lineaddr, &de)) { 284260684Skaiw warnx("dwarf_lineaddr: %s", 285260684Skaiw dwarf_errmsg(de)); 286260684Skaiw goto out; 287260684Skaiw } 288260684Skaiw if (dwarf_lineno(lbuf[i], &lineno, &de)) { 289260684Skaiw warnx("dwarf_lineno: %s", 290260684Skaiw dwarf_errmsg(de)); 291260684Skaiw goto out; 292260684Skaiw } 293260684Skaiw if (dwarf_linesrc(lbuf[i], &file0, &de)) { 294260684Skaiw warnx("dwarf_linesrc: %s", 295260684Skaiw dwarf_errmsg(de)); 296260684Skaiw } else 297260684Skaiw file = file0; 298260684Skaiw if (addr == lineaddr) 299260684Skaiw goto out; 300260684Skaiw else if (addr < lineaddr && addr > plineaddr) { 301260684Skaiw lineno = plineno; 302260684Skaiw file = pfile; 303260684Skaiw goto out; 304260684Skaiw } 305260684Skaiw plineaddr = lineaddr; 306260684Skaiw plineno = lineno; 307260684Skaiw pfile = file; 308260684Skaiw } 309289071Semaste next_cu: 310289071Semaste if (die != NULL) { 311289071Semaste dwarf_dealloc(dbg, die, DW_DLA_DIE); 312289071Semaste die = NULL; 313289071Semaste } 314260684Skaiw } 315260684Skaiw 316260684Skaiwout: 317260684Skaiw funcname = NULL; 318289071Semaste if (ret == DW_DLV_OK && func) { 319260684Skaiw search_func(dbg, die, addr, &funcname); 320289071Semaste die = NULL; 321289071Semaste } 322260684Skaiw 323260684Skaiw if (func) { 324260684Skaiw if (funcname == NULL) 325289071Semaste if ((funcname = strdup(unknown)) == NULL) 326289071Semaste err(EXIT_FAILURE, "strdup"); 327260684Skaiw if (demangle && 328260684Skaiw !elftc_demangle(funcname, demangled, sizeof(demangled), 0)) 329260684Skaiw printf("%s\n", demangled); 330260684Skaiw else 331260684Skaiw printf("%s\n", funcname); 332289071Semaste free(funcname); 333260684Skaiw } 334260684Skaiw 335260684Skaiw (void) printf("%s:%ju\n", base ? basename(file) : file, lineno); 336260684Skaiw 337289071Semaste if (die != NULL) 338289071Semaste dwarf_dealloc(dbg, die, DW_DLA_DIE); 339289071Semaste 340260684Skaiw /* 341260684Skaiw * Reset internal CU pointer, so we will start from the first CU 342260684Skaiw * next round. 343260684Skaiw */ 344260684Skaiw while (ret != DW_DLV_NO_ENTRY) { 345260684Skaiw if (ret == DW_DLV_ERROR) 346260684Skaiw errx(EXIT_FAILURE, "dwarf_next_cu_header: %s", 347260684Skaiw dwarf_errmsg(de)); 348260684Skaiw ret = dwarf_next_cu_header(dbg, NULL, NULL, NULL, NULL, NULL, 349260684Skaiw &de); 350260684Skaiw } 351260684Skaiw} 352260684Skaiw 353260684Skaiwstatic void 354260684Skaiwfind_section_base(const char *exe, Elf *e, const char *section) 355260684Skaiw{ 356260684Skaiw Dwarf_Addr off; 357260684Skaiw Elf_Scn *scn; 358260684Skaiw GElf_Ehdr eh; 359260684Skaiw GElf_Shdr sh; 360260684Skaiw size_t shstrndx; 361260684Skaiw int elferr; 362260684Skaiw const char *name; 363260684Skaiw 364260684Skaiw if (gelf_getehdr(e, &eh) != &eh) { 365260684Skaiw warnx("gelf_getehdr failed: %s", elf_errmsg(-1)); 366260684Skaiw return; 367260684Skaiw } 368260684Skaiw 369260684Skaiw if (!elf_getshstrndx(e, &shstrndx)) { 370260684Skaiw warnx("elf_getshstrndx failed: %s", elf_errmsg(-1)); 371260684Skaiw return; 372260684Skaiw } 373260684Skaiw 374260684Skaiw (void) elf_errno(); 375260684Skaiw off = 0; 376260684Skaiw scn = NULL; 377260684Skaiw while ((scn = elf_nextscn(e, scn)) != NULL) { 378260684Skaiw if (gelf_getshdr(scn, &sh) == NULL) { 379260684Skaiw warnx("gelf_getshdr failed: %s", elf_errmsg(-1)); 380260684Skaiw continue; 381260684Skaiw } 382260684Skaiw if ((name = elf_strptr(e, shstrndx, sh.sh_name)) == NULL) 383260684Skaiw goto next; 384260684Skaiw if (!strcmp(section, name)) { 385260684Skaiw if (eh.e_type == ET_EXEC || eh.e_type == ET_DYN) { 386260684Skaiw /* 387260684Skaiw * For executables, section base is the virtual 388260684Skaiw * address of the specified section. 389260684Skaiw */ 390260684Skaiw section_base = sh.sh_addr; 391260684Skaiw } else if (eh.e_type == ET_REL) { 392260684Skaiw /* 393260684Skaiw * For relocatables, section base is the 394260684Skaiw * relative offset of the specified section 395260684Skaiw * to the start of the first section. 396260684Skaiw */ 397260684Skaiw section_base = off; 398260684Skaiw } else 399260684Skaiw warnx("unknown e_type %u", eh.e_type); 400260684Skaiw return; 401260684Skaiw } 402260684Skaiw next: 403260684Skaiw off += sh.sh_size; 404260684Skaiw } 405260684Skaiw elferr = elf_errno(); 406260684Skaiw if (elferr != 0) 407260684Skaiw warnx("elf_nextscn failed: %s", elf_errmsg(elferr)); 408260684Skaiw 409260684Skaiw errx(EXIT_FAILURE, "%s: cannot find section %s", exe, section); 410260684Skaiw} 411260684Skaiw 412260684Skaiwint 413260684Skaiwmain(int argc, char **argv) 414260684Skaiw{ 415260684Skaiw Elf *e; 416260684Skaiw Dwarf_Debug dbg; 417260684Skaiw Dwarf_Error de; 418260684Skaiw const char *exe, *section; 419260684Skaiw char line[1024]; 420260684Skaiw int fd, i, opt; 421260684Skaiw 422260684Skaiw exe = NULL; 423260684Skaiw section = NULL; 424260684Skaiw while ((opt = getopt_long(argc, argv, "b:Ce:fj:sHV", longopts, NULL)) != 425260684Skaiw -1) { 426260684Skaiw switch (opt) { 427260684Skaiw case 'b': 428260684Skaiw /* ignored */ 429260684Skaiw break; 430260684Skaiw case 'C': 431260684Skaiw demangle = 1; 432260684Skaiw break; 433260684Skaiw case 'e': 434260684Skaiw exe = optarg; 435260684Skaiw break; 436260684Skaiw case 'f': 437260684Skaiw func = 1; 438260684Skaiw break; 439260684Skaiw case 'j': 440260684Skaiw section = optarg; 441260684Skaiw break; 442260684Skaiw case 's': 443260684Skaiw base = 1; 444260684Skaiw break; 445260684Skaiw case 'H': 446260684Skaiw usage(); 447260684Skaiw case 'V': 448260684Skaiw version(); 449260684Skaiw default: 450260684Skaiw usage(); 451260684Skaiw } 452260684Skaiw } 453260684Skaiw 454260684Skaiw argv += optind; 455260684Skaiw argc -= optind; 456260684Skaiw 457260684Skaiw if (exe == NULL) 458260684Skaiw exe = "a.out"; 459260684Skaiw 460260684Skaiw if ((fd = open(exe, O_RDONLY)) < 0) 461260684Skaiw err(EXIT_FAILURE, "%s", exe); 462260684Skaiw 463260684Skaiw if (dwarf_init(fd, DW_DLC_READ, NULL, NULL, &dbg, &de)) 464260684Skaiw errx(EXIT_FAILURE, "dwarf_init: %s", dwarf_errmsg(de)); 465260684Skaiw 466260684Skaiw if (dwarf_get_elf(dbg, &e, &de) != DW_DLV_OK) 467260684Skaiw errx(EXIT_FAILURE, "dwarf_get_elf: %s", dwarf_errmsg(de)); 468260684Skaiw 469260684Skaiw if (section) 470260684Skaiw find_section_base(exe, e, section); 471260684Skaiw else 472260684Skaiw section_base = 0; 473260684Skaiw 474260684Skaiw if (argc > 0) 475260684Skaiw for (i = 0; i < argc; i++) 476260684Skaiw translate(dbg, argv[i]); 477260684Skaiw else 478276689Semaste while (fgets(line, sizeof(line), stdin) != NULL) { 479260684Skaiw translate(dbg, line); 480276689Semaste fflush(stdout); 481276689Semaste } 482260684Skaiw 483260684Skaiw dwarf_finish(dbg, &de); 484260684Skaiw 485260684Skaiw (void) elf_end(e); 486260684Skaiw 487260684Skaiw exit(0); 488260684Skaiw} 489