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 2008 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 #include	<stdio.h>
29 #include	<ctype.h>
30 #include	<unistd.h>
31 #include	<machdep.h>
32 #include	<elfedit.h>
33 #include	<strings.h>
34 #include	<debug.h>
35 #include	<conv.h>
36 #include	<str_msg.h>
37 
38 
39 
40 
41 #define	MAXNDXSIZE	10
42 
43 
44 
45 /*
46  * This module uses shared code for several of the commands.
47  * It is sometimes necessary to know which specific command
48  * is active.
49  */
50 typedef enum {
51 	STR_CMD_T_DUMP =	0,	/* str:dump */
52 	STR_CMD_T_SET =		1,	/* str:set */
53 	STR_CMD_T_ADD =		2,	/* str:add */
54 	STR_CMD_T_ZERO =	3,	/* str:zero */
55 } STR_CMD_T;
56 
57 
58 
59 #ifndef _ELF64
60 /*
61  * We supply this function for the msg module. Only one copy is needed.
62  */
63 const char *
64 _str_msg(Msg mid)
65 {
66 	return (gettext(MSG_ORIG(mid)));
67 }
68 
69 #endif
70 
71 
72 
73 /*
74  * This function is supplied to elfedit through our elfedit_module_t
75  * definition. It translates the opaque elfedit_i18nhdl_t handles
76  * in our module interface into the actual strings for elfedit to
77  * use.
78  *
79  * note:
80  *	This module uses Msg codes for its i18n handle type.
81  *	So the translation is simply to use MSG_INTL() to turn
82  *	it into a string and return it.
83  */
84 static const char *
85 mod_i18nhdl_to_str(elfedit_i18nhdl_t hdl)
86 {
87 	Msg msg = (Msg)hdl;
88 
89 	return (MSG_INTL(msg));
90 }
91 
92 
93 
94 /*
95  * The sym_opt_t enum specifies a bit value for every optional
96  * argument allowed by a command in this module.
97  */
98 typedef enum {
99 	STR_OPT_F_ANY =		1,	/* -any: treat any sec. as strtab */
100 	STR_OPT_F_END =		2,	/* -end: zero to end of strtab */
101 	STR_OPT_F_NOTERM =	4,	/* -noterm: str:set won't term string */
102 	STR_OPT_F_SHNAME =	8,	/* -shnam name: section spec. by name */
103 	STR_OPT_F_SHNDX =	16,	/* -shndx ndx: strtab spec. by index */
104 	STR_OPT_F_SHTYP =	32,	/* -shtyp type: section spec. by type */
105 	STR_OPT_F_STRNDX =	64,	/* -strndx: String specified by index */
106 } str_opt_t;
107 
108 
109 /*
110  * A variable of type ARGSTATE is used by each command to maintain
111  * information about the string table section being used, and for any
112  * auxiliary sections that are related to it.
113  */
114 typedef struct {
115 	elfedit_obj_state_t	*obj_state;
116 	str_opt_t		optmask;   	/* Mask of options used */
117 	int			argc;		/* # of plain arguments */
118 	const char		**argv;		/* Plain arguments */
119 
120 	struct {				/* String table */
121 		elfedit_section_t	*sec;
122 		Word			ndx;	/* Table offset if (argc > 0) */
123 	} str;
124 	struct {				/* Dynamic section */
125 		elfedit_section_t	*sec;
126 		Dyn			*data;
127 		Word			n;
128 		elfedit_dyn_elt_t	strpad;
129 	} dyn;
130 } ARGSTATE;
131 
132 
133 
134 /*
135  * Given an ELF SHT_ section type constant, shndx_to_strtab() returns
136  * one of the following
137  */
138 
139 typedef enum {
140 	SHTOSTR_NONE = 0,		/* Type can't lead to a  string table */
141 	SHTOSTR_STRTAB = 1,		/* type is SHT_STRTAB */
142 	SHTOSTR_LINK_STRTAB = 2,	/* sh_link for type yields strtab */
143 	SHTOSTR_LINK_SYMTAB = 3,	/* sh_link for type yields symtab */
144 } SHTOSTR_T;
145 
146 static int
147 shtype_to_strtab(Word sh_type)
148 {
149 	switch (sh_type) {
150 	case SHT_STRTAB:
151 		return (SHTOSTR_STRTAB);
152 
153 	/* These sections reference a string table via sh_link */
154 	case SHT_DYNAMIC:
155 	case SHT_SYMTAB:
156 	case SHT_DYNSYM:
157 	case SHT_SUNW_LDYNSYM:
158 	case SHT_SUNW_verdef:
159 	case SHT_SUNW_verneed:
160 		return (SHTOSTR_LINK_STRTAB);
161 
162 	/*
163 	 * These sections reference a symbol table via sh_link.
164 	 * Symbol tables, in turn, reference a string table
165 	 * via their sh_link.
166 	 */
167 	case SHT_HASH:
168 	case SHT_REL:
169 	case SHT_RELA:
170 	case SHT_GROUP:
171 	case SHT_SYMTAB_SHNDX:
172 	case SHT_SUNW_move:
173 	case SHT_SUNW_syminfo:
174 	case SHT_SUNW_versym:
175 	case SHT_SUNW_symsort:
176 	case SHT_SUNW_tlssort:
177 		return (SHTOSTR_LINK_SYMTAB);
178 	}
179 
180 	/* Types that lead to string tables were caught above */
181 	return (SHTOSTR_NONE);
182 }
183 
184 /*
185  * Given a section index, attempt to convert it into an index
186  * to a string table section.
187  */
188 static Word
189 shndx_to_strtab(elfedit_obj_state_t *obj_state, Word ndx)
190 {
191 	/*
192 	 * Locate and validate the string table. In the case where
193 	 * a non-string table section is given that references a string
194 	 * table, we will use the referenced table.
195 	 */
196 	if (ndx < obj_state->os_shnum) {
197 		switch (shtype_to_strtab(
198 		    obj_state->os_secarr[ndx].sec_shdr->sh_type)) {
199 
200 		/* Sections that reference a string table via sh_link */
201 		case SHTOSTR_LINK_STRTAB:
202 			ndx = obj_state->os_secarr[ndx].sec_shdr->sh_link;
203 			break;
204 
205 		/*
206 		 * Sections that reference a symbol tabel via sh_link,
207 		 * which in turn reference a string table via their sh_link.
208 		 */
209 		case SHTOSTR_LINK_SYMTAB:
210 			ndx = obj_state->os_secarr[ndx].sec_shdr->sh_link;
211 			if (ndx < obj_state->os_shnum)
212 				ndx =
213 				    obj_state->os_secarr[ndx].sec_shdr->sh_link;
214 			break;
215 		}
216 	}
217 
218 	return (ndx);
219 }
220 
221 
222 
223 /*
224  * Standard argument processing for string table module
225  *
226  * entry
227  *	obj_state, argc, argv - Standard command arguments
228  *	optmask - Mask of allowed optional arguments.
229  *	argstate - Address of ARGSTATE block to be initialized
230  *
231  * exit:
232  *	On success, *argstate is initialized. On error,
233  *	an error is issued and this routine does not return.
234  */
235 static void
236 process_args(elfedit_obj_state_t *obj_state, int argc, const char *argv[],
237     STR_CMD_T cmd, ARGSTATE *argstate, int *print_only)
238 {
239 	elfedit_getopt_state_t	getopt_state;
240 	elfedit_getopt_ret_t	*getopt_ret;
241 	Word			ndx;
242 	int			argc_ok;
243 
244 	bzero(argstate, sizeof (*argstate));
245 	argstate->obj_state = obj_state;
246 
247 	/*
248 	 * By default, we use the section name string table pointed at
249 	 * by the ELF header.
250 	 */
251 	ndx = obj_state->os_ehdr->e_shstrndx;
252 
253 	elfedit_getopt_init(&getopt_state, &argc, &argv);
254 
255 	/* Add each new option to the options mask */
256 	while ((getopt_ret = elfedit_getopt(&getopt_state)) != NULL) {
257 		argstate->optmask |= getopt_ret->gor_idmask;
258 
259 		switch (getopt_ret->gor_idmask) {
260 		case STR_OPT_F_SHNAME:		/* -shnam name */
261 			ndx = elfedit_name_to_shndx(obj_state,
262 			    getopt_ret->gor_value);
263 			break;
264 
265 		case STR_OPT_F_SHNDX:		/* -shndx index */
266 			ndx = elfedit_atoui(getopt_ret->gor_value, NULL);
267 			break;
268 
269 		case STR_OPT_F_SHTYP:		/* -shtyp type */
270 			ndx = elfedit_type_to_shndx(obj_state,
271 			    elfedit_atoconst(getopt_ret->gor_value,
272 			    ELFEDIT_CONST_SHT));
273 			break;
274 		}
275 	}
276 
277 	/*
278 	 * Usage error if there are the wrong number of plain arguments.
279 	 */
280 	switch (cmd) {
281 	case STR_CMD_T_DUMP:
282 		argc_ok = (argc == 0) || (argc == 1);
283 		*print_only = 1;
284 		break;
285 	case STR_CMD_T_SET:
286 		argc_ok = (argc == 1) || (argc == 2);
287 		*print_only = (argc == 1);
288 		break;
289 	case STR_CMD_T_ADD:
290 		argc_ok = (argc == 1);
291 		*print_only = 0;
292 		break;
293 	case STR_CMD_T_ZERO:
294 		/*
295 		 * The second argument (count) and the -end option are
296 		 * mutally exclusive.
297 		 */
298 		argc_ok = ((argc == 1) || (argc == 2)) &&
299 		    !((argc == 2) && (argstate->optmask & STR_OPT_F_END));
300 		*print_only = 0;
301 		break;
302 	default:
303 		argc_ok = 0;	/* Unknown command? */
304 		break;
305 	}
306 	if (!argc_ok)
307 		elfedit_command_usage();
308 
309 	/* If there may be an arbitrary amount of output, use a pager */
310 	if (argc == 0)
311 		elfedit_pager_init();
312 
313 	/* Return the updated values of argc/argv */
314 	argstate->argc = argc;
315 	argstate->argv = argv;
316 
317 	if (argstate->optmask & STR_OPT_F_ANY) {
318 		/* Take the arbitrary section */
319 		argstate->str.sec = elfedit_sec_get(obj_state, ndx);
320 
321 	} else {
322 		/*
323 		 * Locate and validate the string table. In the case where
324 		 * a non-string table section is given that references a string
325 		 * table, we will use the referenced table.
326 		 */
327 		ndx = shndx_to_strtab(obj_state, ndx);
328 
329 		/*
330 		 * If ndx is a string table, the following will issue the
331 		 * proper debug messages. If it is out of range, or of any
332 		 * other type, an error is issued and it doesn't return.
333 		 */
334 		argstate->str.sec = elfedit_sec_getstr(obj_state, ndx);
335 	}
336 
337 	/*
338 	 * If there is a dynamic section, check its sh_link to the
339 	 * string table index. If these match, then we have the
340 	 * dynamic string table. In that case, fetch the dynamic
341 	 * section and locate the DT_SUNW_STRPAD entry, causing
342 	 * debug messages to be issued.
343 	 */
344 	argstate->dyn.sec = NULL;
345 	elfedit_dyn_elt_init(&argstate->dyn.strpad);
346 	if (obj_state->os_dynndx != SHN_UNDEF) {
347 		elfedit_section_t *dynsec =
348 		    &obj_state->os_secarr[obj_state->os_dynndx];
349 
350 		if ((dynsec->sec_shdr->sh_type == SHT_DYNAMIC) &&
351 		    (argstate->str.sec->sec_shndx ==
352 		    dynsec->sec_shdr->sh_link)) {
353 			argstate->dyn.sec = elfedit_sec_getdyn(obj_state,
354 			    &argstate->dyn.data, &argstate->dyn.n);
355 			(void) elfedit_dynstr_getpad(dynsec,
356 			    &argstate->dyn.strpad);
357 
358 			/*
359 			 * Does the pad value make sense?
360 			 * Issue debug message and ignore it if not.
361 			 */
362 			if ((argstate->dyn.strpad.dn_seen != 0) &&
363 			    (argstate->dyn.strpad.dn_dyn.d_un.d_val >
364 			    argstate->str.sec->sec_data->d_size)) {
365 				argstate->dyn.strpad.dn_seen = 0;
366 				elfedit_msg(ELFEDIT_MSG_DEBUG,
367 				    MSG_INTL(MSG_DEBUG_BADSTRPAD),
368 				    EC_WORD(argstate->str.sec->sec_shndx),
369 				    argstate->str.sec->sec_name,
370 				    EC_XWORD(argstate->dyn.strpad.dn_dyn.
371 				    d_un.d_val),
372 				    EC_XWORD(argstate->str.sec->
373 				    sec_data->d_size));
374 
375 			}
376 		}
377 	}
378 
379 	/* Locate the string table offset if argument is present */
380 	if ((argc > 0) && (cmd != STR_CMD_T_ADD)) {
381 		/*
382 		 * If the -strndx option was specified, arg is an index
383 		 * into the string table. Otherwise it is a string
384 		 * to be looked up.
385 		 */
386 		if (argstate->optmask & STR_OPT_F_STRNDX) {
387 			argstate->str.ndx = (elfedit_atoui_range(argv[0],
388 			    MSG_ORIG(MSG_STR_STRING), 0,
389 			    argstate->str.sec->sec_data->d_size - 1, NULL));
390 		} else {
391 			if (elfedit_sec_findstr(argstate->str.sec, 0, argv[0],
392 			    &argstate->str.ndx) == 0)
393 				elfedit_msg(ELFEDIT_MSG_ERR,
394 				    MSG_INTL(MSG_ERR_STRNOTFND),
395 				    EC_WORD(argstate->str.sec->sec_shndx),
396 				    argstate->str.sec->sec_name, argv[0]);
397 		}
398 	} else {
399 		argstate->str.ndx = 0;
400 	}
401 }
402 
403 
404 
405 /*
406  * Print string table values, taking output style into account.
407  *
408  * entry:
409  *	autoprint - If True, output is only produced if the elfedit
410  *		autoprint flag is set. If False, output is always produced.
411  *	argstate - State block for current symbol table.
412  */
413 static void
414 print_strtab(int autoprint, ARGSTATE *argstate)
415 {
416 	char			index[(MAXNDXSIZE * 2) + 4];
417 	elfedit_outstyle_t	outstyle;
418 	const char		*str, *limit, *tbl_limit;
419 	Word			ndx;
420 
421 
422 	if (autoprint && ((elfedit_flags() & ELFEDIT_F_AUTOPRINT) == 0))
423 		return;
424 
425 	outstyle = elfedit_outstyle();
426 	if (outstyle == ELFEDIT_OUTSTYLE_DEFAULT) {
427 		elfedit_printf(MSG_INTL(MSG_FMT_STRTAB),
428 		    argstate->str.sec->sec_name);
429 		if (argstate->dyn.strpad.dn_seen)
430 			elfedit_printf(MSG_INTL(MSG_FMT_DYNSTRPAD),
431 			    EC_WORD(argstate->str.sec->sec_data->d_size -
432 			    argstate->dyn.strpad.dn_dyn.d_un.d_val),
433 			    EC_WORD(argstate->str.sec->sec_data->d_size - 1),
434 			    EC_WORD(argstate->dyn.strpad.dn_dyn.d_un.d_val));
435 		elfedit_printf(MSG_INTL(MSG_FMT_DUMPTITLE));
436 	}
437 
438 	str = argstate->str.sec->sec_data->d_buf;
439 	tbl_limit = str + argstate->str.sec->sec_data->d_size;
440 	ndx = argstate->str.ndx;
441 	if (argstate->argc > 0) {
442 		str += ndx;
443 		/*
444 		 * If first byte is NULL and this is the default output style,
445 		 * then we want to display the range of NULL bytes, and we
446 		 * push limit out to the last one in the sequence. Otherwise,
447 		 * just display the string.
448 		 */
449 		if ((*str == '\0') && (outstyle == ELFEDIT_OUTSTYLE_DEFAULT)) {
450 			limit = str;
451 			while (((limit + 1) < tbl_limit) &&
452 			    (*(limit + 1) == '\0'))
453 				limit++;
454 		} else {
455 			limit = str + strlen(str) + 1;
456 		}
457 	} else {
458 		/* Display the entire string table  */
459 		limit = tbl_limit;
460 	}
461 
462 
463 	while (str < limit) {
464 		Word	skip = strlen(str) + 1;
465 		Word	start_ndx;
466 
467 		if (outstyle != ELFEDIT_OUTSTYLE_DEFAULT) {
468 			elfedit_printf(MSG_ORIG(MSG_FMT_STRNL), str);
469 			str += skip;
470 			ndx += skip;
471 			continue;
472 		}
473 
474 		start_ndx = ndx;
475 		if (*str == '\0')
476 			while (((str + 1) < limit) && (*(str + 1) == '\0')) {
477 				ndx++;
478 				str++;
479 			}
480 
481 		if (start_ndx != ndx) {
482 			(void) snprintf(index, sizeof (index),
483 			    MSG_ORIG(MSG_FMT_INDEXRANGE),
484 			    EC_XWORD(start_ndx), EC_XWORD(ndx));
485 		} else {
486 			(void) snprintf(index, sizeof (index),
487 			    MSG_ORIG(MSG_FMT_INDEX), EC_XWORD(ndx));
488 		}
489 		elfedit_printf(MSG_ORIG(MSG_FMT_DUMPENTRY), index);
490 		elfedit_write(MSG_ORIG(MSG_STR_DQUOTE), MSG_STR_DQUOTE_SIZE);
491 		if (start_ndx == ndx)
492 			elfedit_str_to_c_literal(str, elfedit_write);
493 		elfedit_write(MSG_ORIG(MSG_STR_DQUOTENL),
494 		    MSG_STR_DQUOTENL_SIZE);
495 		str += skip;
496 		ndx += skip;
497 	}
498 }
499 
500 
501 /*
502  * Command body for str:set, handling the case where the 3rd
503  * argument (new-str) is present.
504  */
505 static elfedit_cmdret_t
506 cmd_body_set(ARGSTATE *argstate)
507 {
508 	elfedit_section_t	*strsec = argstate->str.sec;
509 	const char		*newstr = argstate->argv[1];
510 	Word	ndx = argstate->str.ndx;
511 	char	*oldstr;
512 	int	i, len, ncp;
513 
514 	len = strlen(newstr);
515 	ncp = len;
516 	if (!(argstate->optmask & STR_OPT_F_NOTERM))
517 		ncp++;
518 
519 	/* NULL string with no termination? Nothing to do */
520 	if (ncp == 0)
521 		return (ELFEDIT_CMDRET_NONE);
522 
523 	/* Does it fit? */
524 	if ((ndx + ncp) > strsec->sec_data->d_size)
525 		elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_NOFIT),
526 		    EC_WORD(strsec->sec_shndx), strsec->sec_name,
527 		    EC_WORD(ndx), newstr);
528 
529 	/* Does it clobber the final NULL termination? */
530 	if (((ndx + ncp) == strsec->sec_data->d_size) &&
531 	    (argstate->optmask & STR_OPT_F_NOTERM))
532 		elfedit_msg(ELFEDIT_MSG_ERR, MSG_INTL(MSG_ERR_FINALNULL),
533 		    EC_WORD(strsec->sec_shndx), strsec->sec_name,
534 		    EC_WORD(ndx), newstr);
535 
536 	/*
537 	 * strtab[0] is always supposed to contain a NULL byte. You're not
538 	 * supposed to mess with it. We will carry out this operation,
539 	 * but with a debug message indicating that it is unorthodox.
540 	 */
541 	if ((ndx == 0) && (*newstr != '\0'))
542 		elfedit_msg(ELFEDIT_MSG_DEBUG, MSG_INTL(MSG_DEBUG_CHGSTR0),
543 		    EC_WORD(strsec->sec_shndx), strsec->sec_name,
544 		    EC_WORD(ndx), newstr);
545 
546 	/* Does it alter the existing value? */
547 	oldstr = ndx + (char *)strsec->sec_data->d_buf;
548 	for (i = 0; i < ncp; i++)
549 		if (newstr[i] != oldstr[i])
550 			break;
551 	if (i == ncp) {		/* No change */
552 		elfedit_msg(ELFEDIT_MSG_DEBUG, MSG_INTL(MSG_DEBUG_S_OK),
553 		    strsec->sec_shndx, strsec->sec_name, ndx, newstr);
554 		return (ELFEDIT_CMDRET_NONE);
555 	}
556 
557 	/*
558 	 * If the new string is longer than the old one, then it will
559 	 * clobber the start of the following string. The resulting
560 	 * string table is perfectly legal, but issue a debug message
561 	 * letting the user know.
562 	 */
563 	i = strlen(oldstr);
564 	if (len > i)
565 		elfedit_msg(ELFEDIT_MSG_DEBUG, MSG_INTL(MSG_DEBUG_LONGSTR),
566 		    EC_WORD(strsec->sec_shndx), strsec->sec_name,
567 		    EC_WORD(ndx), len, i);
568 
569 	/*
570 	 * If we have strayed into the reserved part of the dynstr, then
571 	 * update DT_SUNW_STRPAD.
572 	 */
573 	if (argstate->dyn.strpad.dn_seen) {
574 		elfedit_dyn_elt_t	*strpad = &argstate->dyn.strpad;
575 		Word	new_pad_ndx = ndx + len + 1;
576 		Word	pad_ndx = argstate->str.sec->sec_data->d_size -
577 		    strpad->dn_dyn.d_un.d_val;
578 
579 		if (new_pad_ndx > pad_ndx) {
580 			elfedit_msg(ELFEDIT_MSG_DEBUG,
581 			    MSG_INTL(MSG_DEBUG_ADDDYNSTR),
582 			    EC_WORD(strsec->sec_shndx), strsec->sec_name,
583 			    EC_WORD(ndx), EC_WORD(new_pad_ndx - pad_ndx),
584 			    EC_WORD(strpad->dn_dyn.d_un.d_val),
585 			    newstr);
586 
587 			strpad->dn_dyn.d_un.d_val =
588 			    argstate->dyn.data[strpad->dn_ndx].d_un.d_val =
589 			    (argstate->str.sec->sec_data->d_size - new_pad_ndx);
590 			elfedit_modified_data(argstate->dyn.sec);
591 		}
592 	}
593 
594 
595 
596 	elfedit_msg(ELFEDIT_MSG_DEBUG, MSG_INTL(MSG_DEBUG_S_CHG),
597 	    strsec->sec_shndx, strsec->sec_name, ndx, len, oldstr, newstr);
598 	bcopy(newstr, oldstr, ncp);
599 
600 	return (ELFEDIT_CMDRET_MOD);
601 }
602 
603 
604 /*
605  * Command body for str:zero
606  */
607 static elfedit_cmdret_t
608 cmd_body_zero(ARGSTATE *argstate)
609 {
610 	elfedit_section_t	*strsec = argstate->str.sec;
611 	Word	count;
612 	Word	ndx = argstate->str.ndx;
613 	char	*oldstr = ndx + (char *)strsec->sec_data->d_buf;
614 	Word	i;
615 
616 	/* How many bytes to zero? */
617 	if (argstate->optmask & STR_OPT_F_END)
618 		count = strsec->sec_data->d_size - argstate->str.ndx;
619 	else if (argstate->argc == 2)
620 		count = elfedit_atoui_range(argstate->argv[1],
621 		    MSG_ORIG(MSG_STR_COUNT), 0,
622 		    argstate->str.sec->sec_data->d_size - argstate->str.ndx,
623 		    NULL);
624 	else
625 		count = strlen(oldstr);
626 
627 	/* Does it alter the existing value? */
628 	for (i = 0; i < count; i++)
629 		if (oldstr[i] != '\0')
630 			break;
631 	if (i == count) {		/* No change */
632 		elfedit_msg(ELFEDIT_MSG_DEBUG, MSG_INTL(MSG_DEBUG_Z_OK),
633 		    strsec->sec_shndx, strsec->sec_name, ndx);
634 		return (ELFEDIT_CMDRET_NONE);
635 	}
636 
637 	elfedit_msg(ELFEDIT_MSG_DEBUG, MSG_INTL(MSG_DEBUG_Z_CHG),
638 	    strsec->sec_shndx, strsec->sec_name, ndx, count);
639 	bzero(oldstr, count);
640 
641 	return (ELFEDIT_CMDRET_MOD);
642 }
643 
644 
645 /*
646  * Common body for the str: module commands.
647  *
648  * entry:
649  *	cmd - One of the STR_CMD_T_* constants listed above, specifying
650  *		which command to implement.
651  *	obj_state, argc, argv - Standard command arguments
652  */
653 static elfedit_cmdret_t
654 cmd_body(STR_CMD_T cmd, elfedit_obj_state_t *obj_state,
655     int argc, const char *argv[])
656 {
657 	ARGSTATE		argstate;
658 	elfedit_cmdret_t	ret = ELFEDIT_CMDRET_NONE;
659 	int			print_only;
660 
661 	process_args(obj_state, argc, argv, cmd, &argstate, &print_only);
662 
663 	/*
664 	 * If this call call does not change data, display the current
665 	 * value(s) and return.
666 	 */
667 	if (print_only) {
668 		print_strtab(0, &argstate);
669 		return (ELFEDIT_CMDRET_NONE);
670 	}
671 
672 	switch (cmd) {
673 	/* NOTE: STR_CMD_T_DUMP can't get here --- it's always print_only */
674 
675 	case STR_CMD_T_SET:
676 		ret = cmd_body_set(&argstate);
677 		break;
678 
679 	case STR_CMD_T_ADD:
680 		argstate.str.ndx = elfedit_strtab_insert(obj_state,
681 		    argstate.str.sec, argstate.dyn.sec, argstate.argv[0]);
682 		break;
683 
684 	case STR_CMD_T_ZERO:
685 		ret = cmd_body_zero(&argstate);
686 		break;
687 	}
688 
689 	/*
690 	 * If we modified the strtab section, tell libelf.
691 	 */
692 	if (ret == ELFEDIT_CMDRET_MOD)
693 		elfedit_modified_data(argstate.str.sec);
694 
695 	/* Do autoprint */
696 	print_strtab(1, &argstate);
697 
698 	return (ret);
699 }
700 
701 
702 
703 
704 /*
705  * Command completion functions for the various commands
706  */
707 
708 static void
709 add_shtyp_match(Word sh_type, void *cpldata)
710 {
711 	char		buf[128];
712 	const char	*s;
713 	char		*s2;
714 
715 	s = elfedit_atoconst_value_to_str(ELFEDIT_CONST_SHT, sh_type, 0);
716 	elfedit_cpl_match(cpldata, s, 1);
717 
718 	/*
719 	 * To get the informal tag names that are lowercase
720 	 * and lack the leading SHT_, we copy the string we
721 	 * have into a buffer and process it.
722 	 */
723 	if (strlen(s) < 4)
724 		return;
725 	(void) strlcpy(buf, s + 4, sizeof (buf));
726 	for (s2 = buf; *s2 != '\0'; s2++)
727 		if (isupper(*s2))
728 			*s2 = tolower(*s2);
729 	elfedit_cpl_match(cpldata, buf, 1);
730 }
731 
732 /*
733  * Handle filling in the values for -shnam, -shndx, and -shtyp options.
734  */
735 /*ARGSUSED*/
736 static void
737 cpl_sh_opt(elfedit_obj_state_t *obj_state, void *cpldata, int argc,
738     const char *argv[], int num_opt)
739 {
740 	enum { NAME, INDEX, TYPE }	op;
741 	elfedit_section_t		*sec;
742 	Word 	ndx;
743 
744 	if ((argc != num_opt) || (argc < 2))
745 		return;
746 
747 	if (strcmp(argv[argc - 2], MSG_ORIG(MSG_STR_MINUS_SHNAM)) == 0) {
748 		op = NAME;
749 	} else if (strcmp(argv[argc - 2], MSG_ORIG(MSG_STR_MINUS_SHNDX)) == 0) {
750 		op = INDEX;
751 
752 	} else if (strcmp(argv[argc - 2], MSG_ORIG(MSG_STR_MINUS_SHTYP)) == 0) {
753 		op = TYPE;
754 
755 		if (obj_state == NULL) {	 /* No object available */
756 			elfedit_atoui_sym_t *atoui_sym;
757 
758 			atoui_sym = elfedit_const_to_atoui(ELFEDIT_CONST_SHT);
759 			for (; atoui_sym->sym_name != NULL; atoui_sym++)
760 				if (shtype_to_strtab(atoui_sym->sym_value) !=
761 				    SHTOSTR_NONE)
762 					elfedit_cpl_match(cpldata,
763 					    atoui_sym->sym_name, 1);
764 		}
765 	} else {
766 		return;
767 	}
768 
769 	if (obj_state == NULL)	 /* No object available */
770 		return;
771 
772 	/*
773 	 * Loop over the section headers and supply command completion
774 	 * for the items in the file that can yield a string table.
775 	 */
776 	sec = obj_state->os_secarr;
777 	for (ndx = 0; ndx < obj_state->os_shnum; ndx++, sec++) {
778 		Word sh_type = sec->sec_shdr->sh_type;
779 
780 		if (shtype_to_strtab(sh_type) == SHTOSTR_NONE)
781 			continue;
782 
783 		switch (op) {
784 		case NAME:
785 			elfedit_cpl_match(cpldata, sec->sec_name, 0);
786 			break;
787 		case INDEX:
788 			{
789 				char index[MAXNDXSIZE];
790 
791 				(void) snprintf(index, sizeof (index),
792 				    MSG_ORIG(MSG_FMT_WORDVAL),
793 				    sec->sec_shndx);
794 				elfedit_cpl_match(cpldata, index, 1);
795 			}
796 			break;
797 		case TYPE:
798 			add_shtyp_match(sh_type, cpldata);
799 			break;
800 		}
801 	}
802 }
803 
804 
805 /*
806  * Most of the commands accept an -shXXX option for the string table
807  * and a string first argument. This routine examines which argument
808  * is being processed, and supplies completion for these items.
809  */
810 static void
811 cpl_sec_str(elfedit_obj_state_t *obj_state, void *cpldata, int argc,
812     const char *argv[], int num_opt)
813 {
814 	const char		*str, *limit;
815 	elfedit_section_t	*sec;
816 	Word			strtab_ndx;
817 	Word			ndx;
818 
819 	/* Handle -shXXX options */
820 	cpl_sh_opt(obj_state, cpldata, argc, argv, num_opt);
821 
822 	/* Without object state, there's no data to work from */
823 	if (obj_state == NULL)
824 		return;
825 
826 	/* If not first plain arg, return */
827 	if (argc != (num_opt + 1))
828 		return;
829 
830 	/*
831 	 * Look at the options, looking for two things:
832 	 *	1) A -shXXX option specifying a section. If so, turn that
833 	 *		into a section index if possible.
834 	 *	2) Was -strndx used? If so, we are looking at an integer
835 	 *		value and have nothing to complete.
836 	 */
837 	strtab_ndx = obj_state->os_ehdr->e_shstrndx;
838 	for (ndx = 0; ndx < num_opt; ndx++) {
839 		if (strcmp(argv[ndx], MSG_ORIG(MSG_STR_MINUS_STRNDX)) == 0)
840 			return;
841 
842 		if ((ndx+1) < num_opt) {
843 			if (strcmp(argv[ndx],
844 			    MSG_ORIG(MSG_STR_MINUS_SHNAM)) == 0) {
845 				Word		i;
846 
847 				for (i = 1; i < obj_state->os_shnum; i++)
848 					if (strcmp(obj_state->os_secarr[i].
849 					    sec_name, argv[ndx+1]) == 0) {
850 						strtab_ndx = i;
851 						break;
852 					}
853 			} else if (strcmp(argv[ndx],
854 			    MSG_ORIG(MSG_STR_MINUS_SHNDX)) == 0) {
855 				elfedit_atoui_t val;
856 
857 				if (elfedit_atoui2(argv[ndx+1], NULL,
858 				    &val) != 0)
859 					strtab_ndx = val;
860 			} else if (strcmp(argv[ndx],
861 			    MSG_ORIG(MSG_STR_MINUS_SHTYP)) == 0) {
862 				elfedit_atoui_t	sh_type;
863 				Word		i;
864 
865 				if (elfedit_atoconst2(argv[ndx+1],
866 				    ELFEDIT_CONST_SHT, &sh_type) == 0)
867 					continue;
868 				for (i = 1; i < obj_state->os_shnum; i++)
869 					if (obj_state->os_secarr[i].sec_shdr->
870 					    sh_type == sh_type) {
871 						strtab_ndx = i;
872 						break;
873 					}
874 			}
875 		}
876 	}
877 
878 	/*
879 	 * Locate and validate the string table. In the case where
880 	 * a non-string table section is given that references a string
881 	 * table, we will use the referenced table.
882 	 */
883 	strtab_ndx = shndx_to_strtab(obj_state, strtab_ndx);
884 	if ((strtab_ndx >= obj_state->os_shnum) ||
885 	    (obj_state->os_secarr[strtab_ndx].sec_shdr->sh_type != SHT_STRTAB))
886 		return;
887 	sec = &obj_state->os_secarr[strtab_ndx];
888 
889 	str = sec->sec_data->d_buf;
890 	limit = str + sec->sec_data->d_size;
891 	while (str < limit) {
892 		if (*str != '\0')
893 			elfedit_cpl_match(cpldata, str, 0);
894 		str += strlen(str) + 1;
895 	}
896 }
897 
898 
899 
900 /*
901  * Implementation functions for the commands
902  */
903 static elfedit_cmdret_t
904 cmd_dump(elfedit_obj_state_t *obj_state, int argc, const char *argv[])
905 {
906 	return (cmd_body(STR_CMD_T_DUMP, obj_state, argc, argv));
907 }
908 
909 static elfedit_cmdret_t
910 cmd_set(elfedit_obj_state_t *obj_state, int argc, const char *argv[])
911 {
912 	return (cmd_body(STR_CMD_T_SET, obj_state, argc, argv));
913 }
914 
915 static elfedit_cmdret_t
916 cmd_add(elfedit_obj_state_t *obj_state, int argc, const char *argv[])
917 {
918 	return (cmd_body(STR_CMD_T_ADD, obj_state, argc, argv));
919 }
920 
921 static elfedit_cmdret_t
922 cmd_zero(elfedit_obj_state_t *obj_state, int argc, const char *argv[])
923 {
924 	return (cmd_body(STR_CMD_T_ZERO, obj_state, argc, argv));
925 }
926 
927 
928 
929 /*ARGSUSED*/
930 elfedit_module_t *
931 elfedit_init(elfedit_module_version_t version)
932 {
933 	/* str:dump */
934 	static const char *name_dump[] = {
935 	    MSG_ORIG(MSG_CMD_DUMP),
936 	    MSG_ORIG(MSG_STR_EMPTY),	/* "" makes this the default command */
937 	    NULL
938 	};
939 	static elfedit_cmd_optarg_t opt_dump[] = {
940 		{ MSG_ORIG(MSG_STR_MINUS_ANY),
941 		    /* MSG_INTL(MSG_OPTDESC_ANY) */
942 		    ELFEDIT_I18NHDL(MSG_OPTDESC_ANY), 0,
943 		    STR_OPT_F_ANY, 0 },
944 		{ ELFEDIT_STDOA_OPT_O, NULL,
945 		    ELFEDIT_CMDOA_F_INHERIT, 0, 0 },
946 		{ MSG_ORIG(MSG_STR_MINUS_SHNAM),
947 		    /* MSG_INTL(MSG_OPTDESC_SHNAM) */
948 		    ELFEDIT_I18NHDL(MSG_OPTDESC_SHNAM), ELFEDIT_CMDOA_F_VALUE,
949 		    STR_OPT_F_SHNAME, STR_OPT_F_SHNDX | STR_OPT_F_SHTYP },
950 		{ MSG_ORIG(MSG_STR_NAME), NULL, 0 },
951 		{ MSG_ORIG(MSG_STR_MINUS_SHNDX),
952 		    /* MSG_INTL(MSG_OPTDESC_SHNDX) */
953 		    ELFEDIT_I18NHDL(MSG_OPTDESC_SHNDX), ELFEDIT_CMDOA_F_VALUE,
954 		    STR_OPT_F_SHNDX, STR_OPT_F_SHNAME | STR_OPT_F_SHTYP },
955 		{ MSG_ORIG(MSG_STR_INDEX), NULL, 0 },
956 		{ MSG_ORIG(MSG_STR_MINUS_SHTYP),
957 		    /* MSG_INTL(MSG_OPTDESC_SHTYP) */
958 		    ELFEDIT_I18NHDL(MSG_OPTDESC_SHTYP), ELFEDIT_CMDOA_F_VALUE,
959 		    STR_OPT_F_SHTYP, STR_OPT_F_SHNAME | STR_OPT_F_SHNDX },
960 		{ MSG_ORIG(MSG_STR_TYPE), NULL, 0 },
961 		{ MSG_ORIG(MSG_STR_MINUS_STRNDX),
962 		    /* MSG_INTL(MSG_OPTDESC_STRNDX) */
963 		    ELFEDIT_I18NHDL(MSG_OPTDESC_STRNDX), 0,
964 		    STR_OPT_F_STRNDX, 0 },
965 		{ NULL }
966 	};
967 	static elfedit_cmd_optarg_t arg_dump[] = {
968 		{ MSG_ORIG(MSG_STR_STRING),
969 		    /* MSG_INTL(MSG_A1_STRING) */
970 		    ELFEDIT_I18NHDL(MSG_A1_STRING),
971 		    ELFEDIT_CMDOA_F_OPT },
972 		{ NULL }
973 	};
974 
975 	/* str:set */
976 	static const char *name_set[] = {
977 	    MSG_ORIG(MSG_CMD_SET), NULL };
978 	static elfedit_cmd_optarg_t opt_set[] = {
979 		{ MSG_ORIG(MSG_STR_MINUS_ANY),
980 		    /* MSG_INTL(MSG_OPTDESC_ANY) */
981 		    ELFEDIT_I18NHDL(MSG_OPTDESC_ANY), 0,
982 		    STR_OPT_F_ANY, 0 },
983 		{ ELFEDIT_STDOA_OPT_O, NULL,
984 		    ELFEDIT_CMDOA_F_INHERIT, 0, 0 },
985 		{ MSG_ORIG(MSG_STR_MINUS_NOTERM),
986 		    /* MSG_INTL(MSG_OPTDESC_NOTERM) */
987 		    ELFEDIT_I18NHDL(MSG_OPTDESC_NOTERM), 0,
988 		    STR_OPT_F_NOTERM, 0 },
989 		{ MSG_ORIG(MSG_STR_MINUS_SHNAM),
990 		    /* MSG_INTL(MSG_OPTDESC_SHNAM) */
991 		    ELFEDIT_I18NHDL(MSG_OPTDESC_SHNAM), ELFEDIT_CMDOA_F_VALUE,
992 		    STR_OPT_F_SHNAME, STR_OPT_F_SHNDX | STR_OPT_F_SHTYP },
993 		{ MSG_ORIG(MSG_STR_NAME), NULL, 0 },
994 		{ MSG_ORIG(MSG_STR_MINUS_SHNDX),
995 		    /* MSG_INTL(MSG_OPTDESC_SHNDX) */
996 		    ELFEDIT_I18NHDL(MSG_OPTDESC_SHNDX), ELFEDIT_CMDOA_F_VALUE,
997 		    STR_OPT_F_SHNDX, STR_OPT_F_SHNAME | STR_OPT_F_SHTYP },
998 		{ MSG_ORIG(MSG_STR_INDEX), NULL, 0 },
999 		{ MSG_ORIG(MSG_STR_MINUS_SHTYP),
1000 		    /* MSG_INTL(MSG_OPTDESC_SHTYP) */
1001 		    ELFEDIT_I18NHDL(MSG_OPTDESC_SHTYP), ELFEDIT_CMDOA_F_VALUE,
1002 		    STR_OPT_F_SHTYP, STR_OPT_F_SHNAME | STR_OPT_F_SHNDX },
1003 		{ MSG_ORIG(MSG_STR_TYPE), NULL, 0 },
1004 		{ MSG_ORIG(MSG_STR_MINUS_STRNDX),
1005 		    /* MSG_INTL(MSG_OPTDESC_STRNDX) */
1006 		    ELFEDIT_I18NHDL(MSG_OPTDESC_STRNDX), 0,
1007 		    STR_OPT_F_STRNDX, 0 },
1008 		{ NULL }
1009 	};
1010 	static elfedit_cmd_optarg_t arg_set[] = {
1011 		{ MSG_ORIG(MSG_STR_STRING),
1012 		    /* MSG_INTL(MSG_A1_STRING) */
1013 		    ELFEDIT_I18NHDL(MSG_A1_STRING),
1014 		    0 },
1015 		{ MSG_ORIG(MSG_STR_NEWSTRING),
1016 		    /* MSG_INTL(MSG_A2_NEWSTRING) */
1017 		    ELFEDIT_I18NHDL(MSG_A2_NEWSTRING),
1018 		    ELFEDIT_CMDOA_F_OPT },
1019 		{ NULL }
1020 	};
1021 
1022 	/* str:add */
1023 	static const char *name_add[] = {
1024 	    MSG_ORIG(MSG_CMD_ADD), NULL };
1025 	static elfedit_cmd_optarg_t opt_add[] = {
1026 		{ ELFEDIT_STDOA_OPT_O, NULL,
1027 		    ELFEDIT_CMDOA_F_INHERIT, 0, 0 },
1028 		{ MSG_ORIG(MSG_STR_MINUS_SHNAM),
1029 		    /* MSG_INTL(MSG_OPTDESC_SHNAM) */
1030 		    ELFEDIT_I18NHDL(MSG_OPTDESC_SHNAM), ELFEDIT_CMDOA_F_VALUE,
1031 		    STR_OPT_F_SHNAME, STR_OPT_F_SHNDX | STR_OPT_F_SHTYP },
1032 		{ MSG_ORIG(MSG_STR_NAME), NULL, 0 },
1033 		{ MSG_ORIG(MSG_STR_MINUS_SHNDX),
1034 		    /* MSG_INTL(MSG_OPTDESC_SHNDX) */
1035 		    ELFEDIT_I18NHDL(MSG_OPTDESC_SHNDX), ELFEDIT_CMDOA_F_VALUE,
1036 		    STR_OPT_F_SHNDX, STR_OPT_F_SHNAME | STR_OPT_F_SHTYP },
1037 		{ MSG_ORIG(MSG_STR_INDEX), NULL, 0 },
1038 		{ MSG_ORIG(MSG_STR_MINUS_SHTYP),
1039 		    /* MSG_INTL(MSG_OPTDESC_SHTYP) */
1040 		    ELFEDIT_I18NHDL(MSG_OPTDESC_SHTYP), ELFEDIT_CMDOA_F_VALUE,
1041 		    STR_OPT_F_SHTYP, STR_OPT_F_SHNAME | STR_OPT_F_SHNDX },
1042 		{ MSG_ORIG(MSG_STR_TYPE), NULL, 0 },
1043 		{ NULL }
1044 	};
1045 	static elfedit_cmd_optarg_t arg_add[] = {
1046 		{ MSG_ORIG(MSG_STR_NEWSTRING),
1047 		    /* MSG_INTL(MSG_A1_NEWSTRING) */
1048 		    ELFEDIT_I18NHDL(MSG_A1_NEWSTRING),
1049 		    0 },
1050 		{ NULL }
1051 	};
1052 
1053 	/* str:zero */
1054 	static const char *name_zero[] = {
1055 	    MSG_ORIG(MSG_CMD_ZERO), NULL };
1056 	static elfedit_cmd_optarg_t opt_zero[] = {
1057 		{ MSG_ORIG(MSG_STR_MINUS_ANY),
1058 		    /* MSG_INTL(MSG_OPTDESC_ANY) */
1059 		    ELFEDIT_I18NHDL(MSG_OPTDESC_ANY), 0,
1060 		    STR_OPT_F_ANY, 0 },
1061 		{ ELFEDIT_STDOA_OPT_O, NULL,
1062 		    ELFEDIT_CMDOA_F_INHERIT, 0, 0 },
1063 		{ MSG_ORIG(MSG_STR_MINUS_SHNAM),
1064 		    /* MSG_INTL(MSG_OPTDESC_SHNAM) */
1065 		    ELFEDIT_I18NHDL(MSG_OPTDESC_SHNAM), ELFEDIT_CMDOA_F_VALUE,
1066 		    STR_OPT_F_SHNAME, STR_OPT_F_SHNDX | STR_OPT_F_SHTYP },
1067 		{ MSG_ORIG(MSG_STR_NAME), NULL, 0 },
1068 		{ MSG_ORIG(MSG_STR_MINUS_SHNDX),
1069 		    /* MSG_INTL(MSG_OPTDESC_SHNDX) */
1070 		    ELFEDIT_I18NHDL(MSG_OPTDESC_SHNDX), ELFEDIT_CMDOA_F_VALUE,
1071 		    STR_OPT_F_SHNDX, STR_OPT_F_SHNAME | STR_OPT_F_SHTYP },
1072 		{ MSG_ORIG(MSG_STR_INDEX), NULL, 0 },
1073 		{ MSG_ORIG(MSG_STR_MINUS_SHTYP),
1074 		    /* MSG_INTL(MSG_OPTDESC_SHTYP) */
1075 		    ELFEDIT_I18NHDL(MSG_OPTDESC_SHTYP), ELFEDIT_CMDOA_F_VALUE,
1076 		    STR_OPT_F_SHTYP, STR_OPT_F_SHNAME | STR_OPT_F_SHNDX },
1077 		{ MSG_ORIG(MSG_STR_TYPE), NULL, 0 },
1078 		{ MSG_ORIG(MSG_STR_MINUS_STRNDX),
1079 		    /* MSG_INTL(MSG_OPTDESC_STRNDX) */
1080 		    ELFEDIT_I18NHDL(MSG_OPTDESC_STRNDX), 0,
1081 		    STR_OPT_F_STRNDX, 0 },
1082 		{ MSG_ORIG(MSG_STR_MINUS_END),
1083 		    /* MSG_INTL(MSG_OPTDESC_END) */
1084 		    ELFEDIT_I18NHDL(MSG_OPTDESC_END), 0,
1085 		    STR_OPT_F_END, 0 },
1086 		{ NULL }
1087 	};
1088 	static elfedit_cmd_optarg_t arg_zero[] = {
1089 		{ MSG_ORIG(MSG_STR_STRING),
1090 		    /* MSG_INTL(MSG_A1_STRING) */
1091 		    ELFEDIT_I18NHDL(MSG_A1_STRING),
1092 		    0 },
1093 		{ MSG_ORIG(MSG_STR_COUNT),
1094 		    /* MSG_INTL(MSG_A2_COUNT) */
1095 		    ELFEDIT_I18NHDL(MSG_A2_COUNT),
1096 		    ELFEDIT_CMDOA_F_OPT },
1097 		{ NULL }
1098 	};
1099 
1100 
1101 	static elfedit_cmd_t cmds[] = {
1102 		/* str:dump */
1103 		{ cmd_dump, cpl_sec_str, name_dump,
1104 		    /* MSG_INTL(MSG_DESC_DUMP) */
1105 		    ELFEDIT_I18NHDL(MSG_DESC_DUMP),
1106 		    /* MSG_INTL(MSG_HELP_DUMP) */
1107 		    ELFEDIT_I18NHDL(MSG_HELP_DUMP),
1108 		    opt_dump, arg_dump },
1109 
1110 		/* str:set */
1111 		{ cmd_set, cpl_sec_str, name_set,
1112 		    /* MSG_INTL(MSG_DESC_SET) */
1113 		    ELFEDIT_I18NHDL(MSG_DESC_SET),
1114 		    /* MSG_INTL(MSG_HELP_SET) */
1115 		    ELFEDIT_I18NHDL(MSG_HELP_SET),
1116 		    opt_set, arg_set },
1117 
1118 		/* str:add */
1119 		{ cmd_add, cpl_sh_opt, name_add,
1120 		    /* MSG_INTL(MSG_DESC_ADD) */
1121 		    ELFEDIT_I18NHDL(MSG_DESC_ADD),
1122 		    /* MSG_INTL(MSG_HELP_ADD) */
1123 		    ELFEDIT_I18NHDL(MSG_HELP_ADD),
1124 		    opt_add, arg_add },
1125 
1126 		/* str:zero */
1127 		{ cmd_zero, cpl_sec_str, name_zero,
1128 		    /* MSG_INTL(MSG_DESC_ZERO) */
1129 		    ELFEDIT_I18NHDL(MSG_DESC_ZERO),
1130 		    /* MSG_INTL(MSG_HELP_ZERO) */
1131 		    ELFEDIT_I18NHDL(MSG_HELP_ZERO),
1132 		    opt_zero, arg_zero },
1133 
1134 		{ NULL }
1135 	};
1136 
1137 	static elfedit_module_t module = {
1138 	    ELFEDIT_VER_CURRENT, MSG_ORIG(MSG_MOD_NAME),
1139 	    /* MSG_INTL(MSG_MOD_DESC) */
1140 	    ELFEDIT_I18NHDL(MSG_MOD_DESC),
1141 	    cmds, mod_i18nhdl_to_str };
1142 
1143 	return (&module);
1144 }
1145