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