1 /*
2 * CDDL HEADER START
3 *
4 * The contents of this file are subject to the terms of the
5 * Common Development and Distribution License (the "License").
6 * You may not use this file except in compliance with the License.
7 *
8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 * or http://www.opensolaris.org/os/licensing.
10 * See the License for the specific language governing permissions
11 * and limitations under the License.
12 *
13 * When distributing Covered Code, include this CDDL HEADER in each
14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 * If applicable, add the following below this CDDL HEADER, with the
16 * fields enclosed by brackets "[]" replaced with your own identifying
17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 *
19 * CDDL HEADER END
20 */
21
22 /*
23 * Copyright 2007 Sun Microsystems, Inc. All rights reserved.
24 * Use is subject to license terms.
25 *
26 * Copyright 2011 Jason King. All rights reserved.
27 * Copyright 2012 Joshua M. Clulow <josh@sysmgr.org>
28 * Copyright 2015 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
29 * Copyright 2018, Joyent, Inc.
30 * Copyright 2024 Oxide Computer Company
31 */
32
33 #include <ctype.h>
34 #include <getopt.h>
35 #include <stdio.h>
36 #include <stdlib.h>
37 #include <string.h>
38 #include <sys/hexdump.h>
39 #include <sys/sysmacros.h>
40 #include <sys/elf_SPARC.h>
41
42 #include <libdisasm.h>
43
44 #include "dis_target.h"
45 #include "dis_util.h"
46 #include "dis_list.h"
47
48 int g_demangle; /* Demangle C++ names */
49 int g_quiet; /* Quiet mode */
50 int g_numeric; /* Numeric mode */
51 int g_flags; /* libdisasm language flags */
52 int g_doall; /* true if no functions or sections were given */
53
54 dis_namelist_t *g_funclist; /* list of functions to disassemble, if any */
55 dis_namelist_t *g_seclist; /* list of sections to disassemble, if any */
56
57 /*
58 * Section options for -d, -D, and -s
59 */
60 #define DIS_DATA_RELATIVE 1
61 #define DIS_DATA_ABSOLUTE 2
62 #define DIS_TEXT 3
63
64 /*
65 * libdisasm callback data. Keeps track of current data (function or section)
66 * and offset within that data.
67 */
68 typedef struct dis_buffer {
69 dis_tgt_t *db_tgt; /* current dis target */
70 void *db_data; /* function or section data */
71 uint64_t db_addr; /* address of function start */
72 size_t db_size; /* size of data */
73 uint64_t db_nextaddr; /* next address to be read */
74 } dis_buffer_t;
75
76 #define MINSYMWIDTH 22 /* Minimum width of symbol portion of line */
77
78 /*
79 * Given a symbol+offset as returned by dis_tgt_lookup(), print an appropriately
80 * formatted symbol, based on the offset and current setttings.
81 */
82 void
getsymname(uint64_t addr,const char * symbol,off_t offset,char * buf,size_t buflen)83 getsymname(uint64_t addr, const char *symbol, off_t offset, char *buf,
84 size_t buflen)
85 {
86 if (symbol == NULL || g_numeric) {
87 if (g_flags & DIS_OCTAL)
88 (void) snprintf(buf, buflen, "0%llo", addr);
89 else
90 (void) snprintf(buf, buflen, "0x%llx", addr);
91 } else {
92 if (g_demangle)
93 symbol = dis_demangle(symbol);
94
95 if (offset == 0)
96 (void) snprintf(buf, buflen, "%s", symbol);
97 else if (g_flags & DIS_OCTAL)
98 (void) snprintf(buf, buflen, "%s+0%o", symbol, offset);
99 else
100 (void) snprintf(buf, buflen, "%s+0x%x", symbol, offset);
101 }
102 }
103
104 /*
105 * Determine if we are on an architecture with fixed-size instructions,
106 * and if so, what size they are.
107 */
108 static int
insn_size(dis_handle_t * dhp)109 insn_size(dis_handle_t *dhp)
110 {
111 int min = dis_min_instrlen(dhp);
112 int max = dis_max_instrlen(dhp);
113
114 if (min == max)
115 return (min);
116
117 return (0);
118 }
119
120 /*
121 * The main disassembly routine. Given a fixed-sized buffer and starting
122 * address, disassemble the data using the supplied target and libdisasm handle.
123 */
124 void
dis_data(dis_tgt_t * tgt,dis_handle_t * dhp,uint64_t addr,void * data,size_t datalen)125 dis_data(dis_tgt_t *tgt, dis_handle_t *dhp, uint64_t addr, void *data,
126 size_t datalen)
127 {
128 dis_buffer_t db = { 0 };
129 char buf[BUFSIZE];
130 char symbuf[BUFSIZE];
131 const char *symbol;
132 const char *last_symbol;
133 off_t symoffset;
134 int i;
135 int bytesperline;
136 size_t symsize;
137 int isfunc;
138 size_t symwidth = 0;
139 int ret;
140 int insz = insn_size(dhp);
141
142 db.db_tgt = tgt;
143 db.db_data = data;
144 db.db_addr = addr;
145 db.db_size = datalen;
146
147 dis_set_data(dhp, &db);
148
149 if ((bytesperline = dis_max_instrlen(dhp)) > 6)
150 bytesperline = 6;
151
152 symbol = NULL;
153
154 while (addr < db.db_addr + db.db_size) {
155
156 ret = dis_disassemble(dhp, addr, buf, BUFSIZE);
157 if (ret != 0 && insz > 0) {
158 /*
159 * Since we know instructions are fixed size, we
160 * always know the address of the next instruction
161 */
162 (void) snprintf(buf, sizeof (buf),
163 "*** invalid opcode ***");
164 db.db_nextaddr = addr + insz;
165
166 } else if (ret != 0) {
167 off_t next;
168
169 (void) snprintf(buf, sizeof (buf),
170 "*** invalid opcode ***");
171
172 /*
173 * On architectures with variable sized instructions
174 * we have no way to figure out where the next
175 * instruction starts if we encounter an invalid
176 * instruction. Instead we print the rest of the
177 * instruction stream as hex until we reach the
178 * next valid symbol in the section.
179 */
180 if ((next = dis_tgt_next_symbol(tgt, addr)) == 0) {
181 db.db_nextaddr = db.db_addr + db.db_size;
182 } else {
183 if (next > db.db_size)
184 db.db_nextaddr = db.db_addr +
185 db.db_size;
186 else
187 db.db_nextaddr = addr + next;
188 }
189 }
190
191 /*
192 * Print out the line as:
193 *
194 * address: bytes text
195 *
196 * If there are more than 6 bytes in any given instruction,
197 * spread the bytes across two lines. We try to get symbolic
198 * information for the address, but if that fails we print out
199 * the numeric address instead.
200 *
201 * We try to keep the address portion of the text aligned at
202 * MINSYMWIDTH characters. If we are disassembling a function
203 * with a long name, this can be annoying. So we pick a width
204 * based on the maximum width that the current symbol can be.
205 * This at least produces text aligned within each function.
206 */
207 last_symbol = symbol;
208 symbol = dis_tgt_lookup(tgt, addr, &symoffset, 1, &symsize,
209 &isfunc);
210 if (symbol == NULL) {
211 symbol = dis_find_section(tgt, addr, &symoffset);
212 symsize = symoffset;
213 }
214
215 if (symbol != last_symbol)
216 getsymname(addr, symbol, symsize, symbuf,
217 sizeof (symbuf));
218
219 symwidth = MAX(symwidth, strlen(symbuf));
220 getsymname(addr, symbol, symoffset, symbuf, sizeof (symbuf));
221
222 /*
223 * If we've crossed a new function boundary, print out the
224 * function name on a blank line.
225 */
226 if (!g_quiet && symoffset == 0 && symbol != NULL && isfunc)
227 (void) printf("%s()\n", symbol);
228
229 (void) printf(" %s:%*s ", symbuf,
230 symwidth - strlen(symbuf), "");
231
232 /* print bytes */
233 for (i = 0; i < MIN(bytesperline, (db.db_nextaddr - addr));
234 i++) {
235 int byte = *((uchar_t *)data + (addr - db.db_addr) + i);
236 if (g_flags & DIS_OCTAL)
237 (void) printf("%03o ", byte);
238 else
239 (void) printf("%02x ", byte);
240 }
241
242 /* trailing spaces for missing bytes */
243 for (; i < bytesperline; i++) {
244 if (g_flags & DIS_OCTAL)
245 (void) printf(" ");
246 else
247 (void) printf(" ");
248 }
249
250 /* contents of disassembly */
251 (void) printf(" %s", buf);
252
253 /* excess bytes that spill over onto subsequent lines */
254 for (; i < db.db_nextaddr - addr; i++) {
255 int byte = *((uchar_t *)data + (addr - db.db_addr) + i);
256 if (i % bytesperline == 0)
257 (void) printf("\n %*s ", symwidth, "");
258 if (g_flags & DIS_OCTAL)
259 (void) printf("%03o ", byte);
260 else
261 (void) printf("%02x ", byte);
262 }
263
264 (void) printf("\n");
265
266 addr = db.db_nextaddr;
267 }
268 }
269
270 /*
271 * libdisasm wrapper around symbol lookup. Invoke the target-specific lookup
272 * function, and convert the result using getsymname().
273 */
274 int
do_lookup(void * data,uint64_t addr,char * buf,size_t buflen,uint64_t * start,size_t * symlen)275 do_lookup(void *data, uint64_t addr, char *buf, size_t buflen, uint64_t *start,
276 size_t *symlen)
277 {
278 dis_buffer_t *db = data;
279 const char *symbol;
280 off_t offset;
281 size_t size;
282
283 /*
284 * If NULL symbol is returned, getsymname takes care of
285 * printing appropriate address in buf instead of symbol.
286 */
287 symbol = dis_tgt_lookup(db->db_tgt, addr, &offset, 0, &size, NULL);
288
289 if (buf != NULL)
290 getsymname(addr, symbol, offset, buf, buflen);
291
292 if (start != NULL)
293 *start = addr - offset;
294 if (symlen != NULL)
295 *symlen = size;
296
297 if (symbol == NULL)
298 return (-1);
299
300 return (0);
301 }
302
303 /*
304 * libdisasm wrapper around target reading. libdisasm will always read data
305 * in order, so update our current offset within the buffer appropriately.
306 * We only support reading from within the current object; libdisasm should
307 * never ask us to do otherwise.
308 */
309 int
do_read(void * data,uint64_t addr,void * buf,size_t len)310 do_read(void *data, uint64_t addr, void *buf, size_t len)
311 {
312 dis_buffer_t *db = data;
313 size_t offset;
314
315 if (addr < db->db_addr || addr >= db->db_addr + db->db_size)
316 return (-1);
317
318 offset = addr - db->db_addr;
319 len = MIN(len, db->db_size - offset);
320
321 (void) memcpy(buf, (char *)db->db_data + offset, len);
322
323 db->db_nextaddr = addr + len;
324
325 return (len);
326 }
327
328 /*
329 * Routine to dump raw data in a human-readable format. Used by the -d and -D
330 * options.
331 */
332 void
dump_data(uint64_t addr,void * data,size_t datalen)333 dump_data(uint64_t addr, void *data, size_t datalen)
334 {
335 hexdump_t h;
336
337 hexdump_init(&h);
338 /* Print out data in two-byte chunks. */
339 hexdump_set_grouping(&h, 2);
340 hexdump_set_addr(&h, addr);
341
342 /*
343 * Determine if the address given to us fits in 32-bit range, in which
344 * case use a 4-byte width.
345 */
346 if (((addr + datalen) & 0xffffffff00000000ULL) == 0ULL)
347 hexdump_set_addrwidth(&h, 8);
348 else
349 hexdump_set_addrwidth(&h, 16);
350
351
352 (void) hexdump_fileh(&h, data, datalen, HDF_DEFAULT | HDF_ALIGN,
353 stdout);
354
355 hexdump_fini(&h);
356 }
357
358 /*
359 * Disassemble a section implicitly specified as part of a file. This function
360 * is called for all sections when no other flags are specified. We ignore any
361 * data sections, and print out only those sections containing text.
362 */
363 void
dis_text_section(dis_tgt_t * tgt,dis_scn_t * scn,void * data)364 dis_text_section(dis_tgt_t *tgt, dis_scn_t *scn, void *data)
365 {
366 dis_handle_t *dhp = data;
367
368 /* ignore data sections */
369 if (!dis_section_istext(scn))
370 return;
371
372 if (!g_quiet)
373 (void) printf("\nsection %s\n", dis_section_name(scn));
374
375 dis_data(tgt, dhp, dis_section_addr(scn), dis_section_data(scn),
376 dis_section_size(scn));
377 }
378
379 /*
380 * Structure passed to dis_named_{section,function} which keeps track of both
381 * the target and the libdisasm handle.
382 */
383 typedef struct callback_arg {
384 dis_tgt_t *ca_tgt;
385 dis_handle_t *ca_handle;
386 } callback_arg_t;
387
388 /*
389 * Disassemble a section explicitly named with -s, -d, or -D. The 'type'
390 * argument contains the type of argument given. Pass the data onto the
391 * appropriate helper routine.
392 */
393 void
dis_named_section(dis_scn_t * scn,int type,void * data)394 dis_named_section(dis_scn_t *scn, int type, void *data)
395 {
396 callback_arg_t *ca = data;
397
398 if (!g_quiet)
399 (void) printf("\nsection %s\n", dis_section_name(scn));
400
401 switch (type) {
402 case DIS_DATA_RELATIVE:
403 dump_data(0, dis_section_data(scn), dis_section_size(scn));
404 break;
405 case DIS_DATA_ABSOLUTE:
406 dump_data(dis_section_addr(scn), dis_section_data(scn),
407 dis_section_size(scn));
408 break;
409 case DIS_TEXT:
410 dis_data(ca->ca_tgt, ca->ca_handle, dis_section_addr(scn),
411 dis_section_data(scn), dis_section_size(scn));
412 break;
413 }
414 }
415
416 /*
417 * Disassemble a function explicitly specified with '-F'. The 'type' argument
418 * is unused.
419 */
420 /* ARGSUSED */
421 void
dis_named_function(dis_func_t * func,int type,void * data)422 dis_named_function(dis_func_t *func, int type, void *data)
423 {
424 callback_arg_t *ca = data;
425
426 dis_data(ca->ca_tgt, ca->ca_handle, dis_function_addr(func),
427 dis_function_data(func), dis_function_size(func));
428 }
429
430 /*
431 * Disassemble a complete file. First, we determine the type of the file based
432 * on the ELF machine type, and instantiate a version of the disassembler
433 * appropriate for the file. We then resolve any named sections or functions
434 * against the file, and iterate over the results (or all sections if no flags
435 * were specified).
436 */
437 void
dis_file(const char * filename)438 dis_file(const char *filename)
439 {
440 dis_tgt_t *tgt, *current;
441 dis_scnlist_t *sections;
442 dis_funclist_t *functions;
443 dis_handle_t *dhp;
444 GElf_Ehdr ehdr;
445
446 /*
447 * First, initialize the target
448 */
449 if ((tgt = dis_tgt_create(filename)) == NULL)
450 return;
451
452 if (!g_quiet)
453 (void) printf("disassembly for %s\n\n", filename);
454
455 /*
456 * A given file may contain multiple targets (if it is an archive, for
457 * example). We iterate over all possible targets if this is the case.
458 */
459 for (current = tgt; current != NULL; current = dis_tgt_next(current)) {
460 dis_tgt_ehdr(current, &ehdr);
461
462 /*
463 * Eventually, this should probably live within libdisasm, and
464 * we should be able to disassemble targets from different
465 * architectures. For now, we only support objects as the
466 * native machine type.
467 */
468 switch (ehdr.e_machine) {
469 case EM_SPARC:
470 if (ehdr.e_ident[EI_CLASS] != ELFCLASS32 ||
471 ehdr.e_ident[EI_DATA] != ELFDATA2MSB) {
472 warn("invalid E_IDENT field for SPARC object");
473 return;
474 }
475 g_flags |= DIS_SPARC_V8;
476 break;
477
478 case EM_SPARC32PLUS:
479 {
480 uint64_t flags = ehdr.e_flags & EF_SPARC_32PLUS_MASK;
481
482 if (ehdr.e_ident[EI_CLASS] != ELFCLASS32 ||
483 ehdr.e_ident[EI_DATA] != ELFDATA2MSB) {
484 warn("invalid E_IDENT field for SPARC object");
485 return;
486 }
487
488 if (flags != 0 &&
489 (flags & (EF_SPARC_32PLUS | EF_SPARC_SUN_US1 |
490 EF_SPARC_SUN_US3)) != EF_SPARC_32PLUS)
491 g_flags |= DIS_SPARC_V9 | DIS_SPARC_V9_SGI;
492 else
493 g_flags |= DIS_SPARC_V9;
494 break;
495 }
496
497 case EM_SPARCV9:
498 if (ehdr.e_ident[EI_CLASS] != ELFCLASS64 ||
499 ehdr.e_ident[EI_DATA] != ELFDATA2MSB) {
500 warn("invalid E_IDENT field for SPARC object");
501 return;
502 }
503
504 g_flags |= DIS_SPARC_V9 | DIS_SPARC_V9_SGI;
505 break;
506
507 case EM_386:
508 g_flags |= DIS_X86_SIZE32;
509 break;
510
511 case EM_AMD64:
512 g_flags |= DIS_X86_SIZE64;
513 break;
514
515 case EM_S370:
516 g_flags |= DIS_S370;
517
518 if (ehdr.e_ident[EI_CLASS] != ELFCLASS32 ||
519 ehdr.e_ident[EI_DATA] != ELFDATA2MSB) {
520 warn("invalid E_IDENT field for S370 object");
521 return;
522 }
523 break;
524
525 case EM_S390:
526 /*
527 * Both 390 and z/Architecture use EM_S390, the only
528 * differences is the class: ELFCLASS32 for plain
529 * old s390 and ELFCLASS64 for z/Architecture (aka.
530 * s390x).
531 */
532 if (ehdr.e_ident[EI_CLASS] == ELFCLASS32) {
533 g_flags |= DIS_S390_31;
534 } else if (ehdr.e_ident[EI_CLASS] == ELFCLASS64) {
535 g_flags |= DIS_S390_64;
536 } else {
537 warn("invalid E_IDENT field for S390 object");
538 return;
539 }
540
541 if (ehdr.e_ident[EI_DATA] != ELFDATA2MSB) {
542 warn("invalid E_IDENT field for S390 object");
543 return;
544 }
545 break;
546
547 case EM_RISCV:
548 /*
549 * RISC-V is defined to be litle endian. The current ISA
550 * makes it clear that the 64-bit instructions can
551 * co-exist with the 32-bit ones and therefore we don't
552 * need a separate elf class at this time.
553 */
554 if (ehdr.e_ident[EI_DATA] != ELFDATA2LSB) {
555 warn("invalid EI_DATA field for RISC-V object");
556 return;
557 }
558
559 if (ehdr.e_ident[EI_CLASS] == ELFCLASS32) {
560 g_flags |= DIS_RISCV_32;
561 } else if (ehdr.e_ident[EI_CLASS] == ELFCLASS64) {
562 g_flags |= DIS_RISCV_64;
563 } else {
564 warn("invalid EI_CLASS field for RISC-V "
565 "object");
566 return;
567 }
568 break;
569
570 default:
571 die("%s: unsupported ELF machine 0x%x", filename,
572 ehdr.e_machine);
573 }
574
575 /*
576 * If ET_REL (.o), printing immediate symbols is likely to
577 * result in garbage, as symbol lookups on unrelocated
578 * immediates find false and useless matches.
579 */
580
581 if (ehdr.e_type == ET_REL)
582 g_flags |= DIS_NOIMMSYM;
583
584 if (!g_quiet && dis_tgt_member(current) != NULL)
585 (void) printf("\narchive member %s\n",
586 dis_tgt_member(current));
587
588 /*
589 * Instantiate a libdisasm handle based on the file type.
590 */
591 if ((dhp = dis_handle_create(g_flags, current, do_lookup,
592 do_read)) == NULL)
593 die("%s: failed to initialize disassembler: %s",
594 filename, dis_strerror(dis_errno()));
595
596 if (g_doall) {
597 /*
598 * With no arguments, iterate over all sections and
599 * disassemble only those that contain text.
600 */
601 dis_tgt_section_iter(current, dis_text_section, dhp);
602 } else {
603 callback_arg_t ca;
604
605 ca.ca_tgt = current;
606 ca.ca_handle = dhp;
607
608 /*
609 * If sections or functions were explicitly specified,
610 * resolve those names against the object, and iterate
611 * over just the resulting data.
612 */
613 sections = dis_namelist_resolve_sections(g_seclist,
614 current);
615 functions = dis_namelist_resolve_functions(g_funclist,
616 current);
617
618 dis_scnlist_iter(sections, dis_named_section, &ca);
619 dis_funclist_iter(functions, dis_named_function, &ca);
620
621 dis_scnlist_destroy(sections);
622 dis_funclist_destroy(functions);
623 }
624
625 dis_handle_destroy(dhp);
626 }
627
628 dis_tgt_destroy(tgt);
629 }
630
631 void
usage(void)632 usage(void)
633 {
634 (void) fprintf(stderr, "usage: dis [-CVoqn] [-d sec] \n");
635 (void) fprintf(stderr, "\t[-D sec] [-F function] [-t sec] file ..\n");
636 exit(2);
637 }
638
639 typedef struct lib_node {
640 char *path;
641 struct lib_node *next;
642 } lib_node_t;
643
644 int
main(int argc,char ** argv)645 main(int argc, char **argv)
646 {
647 int optchar;
648 int i;
649 lib_node_t *libs = NULL;
650
651 g_funclist = dis_namelist_create();
652 g_seclist = dis_namelist_create();
653
654 while ((optchar = getopt(argc, argv, "Cd:D:F:l:Lot:Vqn")) != -1) {
655 switch (optchar) {
656 case 'C':
657 g_demangle = 1;
658 break;
659 case 'd':
660 dis_namelist_add(g_seclist, optarg, DIS_DATA_RELATIVE);
661 break;
662 case 'D':
663 dis_namelist_add(g_seclist, optarg, DIS_DATA_ABSOLUTE);
664 break;
665 case 'F':
666 dis_namelist_add(g_funclist, optarg, 0);
667 break;
668 case 'l': {
669 /*
670 * The '-l foo' option historically would attempt to
671 * disassemble '$LIBDIR/libfoo.a'. The $LIBDIR
672 * environment variable has never been supported or
673 * documented for our linker. However, until this
674 * option is formally EOLed, we have to support it.
675 */
676 char *dir;
677 lib_node_t *node;
678 size_t len;
679
680 if ((dir = getenv("LIBDIR")) == NULL ||
681 dir[0] == '\0')
682 dir = "/usr/lib";
683 node = safe_malloc(sizeof (lib_node_t));
684 len = strlen(optarg) + strlen(dir) + sizeof ("/lib.a");
685 node->path = safe_malloc(len);
686
687 (void) snprintf(node->path, len, "%s/lib%s.a", dir,
688 optarg);
689 node->next = libs;
690 libs = node;
691 break;
692 }
693 case 'L':
694 /*
695 * The '-L' option historically would attempt to read
696 * the .debug section of the target to determine source
697 * line information in order to annotate the output.
698 * No compiler has emitted these sections in many years,
699 * and the option has never done what it purported to
700 * do. We silently consume the option for
701 * compatibility.
702 */
703 break;
704 case 'n':
705 g_numeric = 1;
706 break;
707 case 'o':
708 g_flags |= DIS_OCTAL;
709 break;
710 case 'q':
711 g_quiet = 1;
712 break;
713 case 't':
714 dis_namelist_add(g_seclist, optarg, DIS_TEXT);
715 break;
716 case 'V':
717 (void) printf("Solaris disassembler version 1.0\n");
718 return (0);
719 default:
720 usage();
721 break;
722 }
723 }
724
725 argc -= optind;
726 argv += optind;
727
728 if (argc == 0 && libs == NULL) {
729 warn("no objects specified");
730 usage();
731 }
732
733 if (dis_namelist_empty(g_funclist) && dis_namelist_empty(g_seclist))
734 g_doall = 1;
735
736 /*
737 * See comment for 'l' option, above.
738 */
739 while (libs != NULL) {
740 lib_node_t *node = libs->next;
741
742 dis_file(libs->path);
743 free(libs->path);
744 free(libs);
745 libs = node;
746 }
747
748 for (i = 0; i < argc; i++)
749 dis_file(argv[i]);
750
751 dis_namelist_destroy(g_funclist);
752 dis_namelist_destroy(g_seclist);
753
754 return (g_error);
755 }
756