/[gentoo-projects]/pax-utils/scanelf.c
Gentoo

Diff of /pax-utils/scanelf.c

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

Revision 1.76 Revision 1.91
1/* 1/*
2 * Copyright 2003-2005 Gentoo Foundation 2 * Copyright 2003-2005 Gentoo Foundation
3 * Distributed under the terms of the GNU General Public License v2 3 * Distributed under the terms of the GNU General Public License v2
4 * $Header: /var/cvsroot/gentoo-projects/pax-utils/Attic/scanelf.c,v 1.76 2005/06/08 04:24:19 vapier Exp $ 4 * $Header: /var/cvsroot/gentoo-projects/pax-utils/Attic/scanelf.c,v 1.91 2005/12/07 01:04:52 vapier Exp $
5 * 5 *
6 ******************************************************************** 6 * Copyright 2003-2005 Ned Ludd - <solar@gentoo.org>
7 * This program is free software; you can redistribute it and/or 7 * Copyright 2004-2005 Mike Frysinger - <vapier@gentoo.org>
8 * modify it under the terms of the GNU General Public License as
9 * published by the Free Software Foundation; either version 2 of the
10 * License, or (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful, but
13 * WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 * General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston,
20 * MA 02111-1307, USA.
21 */ 8 */
22 9
23#include <stdio.h> 10#include <stdio.h>
24#include <stdlib.h> 11#include <stdlib.h>
25#include <sys/types.h> 12#include <sys/types.h>
31#include <unistd.h> 18#include <unistd.h>
32#include <sys/stat.h> 19#include <sys/stat.h>
33#include <dirent.h> 20#include <dirent.h>
34#include <getopt.h> 21#include <getopt.h>
35#include <assert.h> 22#include <assert.h>
36#include "paxelf.h" 23#include "paxinc.h"
37 24
38static const char *rcsid = "$Id: scanelf.c,v 1.76 2005/06/08 04:24:19 vapier Exp $"; 25static const char *rcsid = "$Id: scanelf.c,v 1.91 2005/12/07 01:04:52 vapier Exp $";
39#define argv0 "scanelf" 26#define argv0 "scanelf"
40 27
41#define IS_MODIFIER(c) (c == '%' || c == '#') 28#define IS_MODIFIER(c) (c == '%' || c == '#')
42 29
43 30
66static char show_textrel = 0; 53static char show_textrel = 0;
67static char show_rpath = 0; 54static char show_rpath = 0;
68static char show_needed = 0; 55static char show_needed = 0;
69static char show_interp = 0; 56static char show_interp = 0;
70static char show_bind = 0; 57static char show_bind = 0;
58static char show_soname = 0;
71static char show_textrels = 0; 59static char show_textrels = 0;
72static char show_banner = 1; 60static char show_banner = 1;
73static char be_quiet = 0; 61static char be_quiet = 0;
74static char be_verbose = 0; 62static char be_verbose = 0;
75static char be_wewy_wewy_quiet = 0; 63static char be_wewy_wewy_quiet = 0;
76static char *find_sym = NULL, *versioned_symname = NULL; 64static char *find_sym = NULL, *versioned_symname = NULL;
77static char *find_lib = NULL; 65static char *find_lib = NULL;
78static char *out_format = NULL; 66static char *out_format = NULL;
79static char *search_path = NULL; 67static char *search_path = NULL;
80 68static char gmatch = 0;
81 69
82 70
83/* sub-funcs for scanelf_file() */ 71/* sub-funcs for scanelf_file() */
72static void scanelf_file_get_symtabs(elfobj *elf, void **sym, void **tab)
73{
74 /* find the best SHT_DYNSYM and SHT_STRTAB sections */
75#define GET_SYMTABS(B) \
76 if (elf->elf_class == ELFCLASS ## B) { \
77 Elf ## B ## _Shdr *symtab, *strtab, *dynsym, *dynstr; \
78 /* debug sections */ \
79 symtab = SHDR ## B (elf_findsecbyname(elf, ".symtab")); \
80 strtab = SHDR ## B (elf_findsecbyname(elf, ".strtab")); \
81 /* runtime sections */ \
82 dynsym = SHDR ## B (elf_findsecbyname(elf, ".dynsym")); \
83 dynstr = SHDR ## B (elf_findsecbyname(elf, ".dynstr")); \
84 if (symtab && dynsym) { \
85 *sym = (void*)((EGET(symtab->sh_size) > EGET(dynsym->sh_size)) ? symtab : dynsym); \
86 } else { \
87 *sym = (void*)(symtab ? symtab : dynsym); \
88 } \
89 if (strtab && dynstr) { \
90 *tab = (void*)((EGET(strtab->sh_size) > EGET(dynstr->sh_size)) ? strtab : dynstr); \
91 } else { \
92 *tab = (void*)(strtab ? strtab : dynstr); \
93 } \
94 }
95 GET_SYMTABS(32)
96 GET_SYMTABS(64)
97}
84static char *scanelf_file_pax(elfobj *elf, char *found_pax) 98static char *scanelf_file_pax(elfobj *elf, char *found_pax)
85{ 99{
86 static char *paxflags;
87 static char ret[7]; 100 static char ret[7];
88 unsigned long i, shown; 101 unsigned long i, shown;
89
90 102
91 if (!show_pax) return NULL; 103 if (!show_pax) return NULL;
92 104
93 shown = 0; 105 shown = 0;
94 memset(&ret, 0, sizeof(ret)); 106 memset(&ret, 0, sizeof(ret));
113 SHOW_PAX(64) 125 SHOW_PAX(64)
114 } 126 }
115 127
116 /* fall back to EI_PAX if no PT_PAX was found */ 128 /* fall back to EI_PAX if no PT_PAX was found */
117 if (!*ret) { 129 if (!*ret) {
130 static char *paxflags;
118 paxflags = pax_short_hf_flags(EI_PAX_FLAGS(elf)); 131 paxflags = pax_short_hf_flags(EI_PAX_FLAGS(elf));
119 if (!be_quiet || (be_quiet && EI_PAX_FLAGS(elf))) { 132 if (!be_quiet || (be_quiet && EI_PAX_FLAGS(elf))) {
120 *found_pax = 1; 133 *found_pax = 1;
121 return paxflags; 134 return (be_wewy_wewy_quiet ? NULL : paxflags);
122 } 135 }
123 strncpy(ret, paxflags, sizeof(ret)); 136 strncpy(ret, paxflags, sizeof(ret));
124 // ++shown;
125 } 137 }
126 138
127 if (be_quiet && !shown) 139 if (be_wewy_wewy_quiet || (be_quiet && !shown))
128 return NULL; 140 return NULL;
141 else
129 return ret; 142 return ret;
130
131} 143}
144
132static char *scanelf_file_phdr(elfobj *elf, char *found_phdr, char *found_relro, char *found_load) 145static char *scanelf_file_phdr(elfobj *elf, char *found_phdr, char *found_relro, char *found_load)
133{ 146{
134 static char ret[12]; 147 static char ret[12];
135 char *found; 148 char *found;
136 unsigned long i, off, shown, check_flags; 149 unsigned long i, shown;
137 unsigned char multi_stack, multi_relro, multi_load; 150 unsigned char multi_stack, multi_relro, multi_load;
138 151
139 if (!show_phdr) return NULL; 152 if (!show_phdr) return NULL;
140 153
141 memcpy(ret, "--- --- ---\0", 12); 154 memcpy(ret, "--- --- ---\0", 12);
142 155
143 shown = 0; 156 shown = 0;
144 multi_stack = multi_relro = multi_load = 0; 157 multi_stack = multi_relro = multi_load = 0;
145 158
146 if (elf->phdr) {
147#define SHOW_PHDR(B) \ 159#define SHOW_PHDR(B) \
148 if (elf->elf_class == ELFCLASS ## B) { \ 160 if (elf->elf_class == ELFCLASS ## B) { \
149 Elf ## B ## _Ehdr *ehdr = EHDR ## B (elf->ehdr); \ 161 Elf ## B ## _Ehdr *ehdr = EHDR ## B (elf->ehdr); \
162 Elf ## B ## _Off offset; \
163 uint32_t flags, check_flags; \
164 if (elf->phdr != NULL) { \
150 Elf ## B ## _Phdr *phdr = PHDR ## B (elf->phdr); \ 165 Elf ## B ## _Phdr *phdr = PHDR ## B (elf->phdr); \
151 uint32_t flags; \
152 for (i = 0; i < EGET(ehdr->e_phnum); i++) { \ 166 for (i = 0; i < EGET(ehdr->e_phnum); ++i) { \
153 if (EGET(phdr[i].p_type) == PT_GNU_STACK) { \ 167 if (EGET(phdr[i].p_type) == PT_GNU_STACK) { \
154 if (multi_stack++) warnf("%s: multiple PT_GNU_STACK's !?", elf->filename); \ 168 if (multi_stack++) warnf("%s: multiple PT_GNU_STACK's !?", elf->filename); \
155 found = found_phdr; \ 169 found = found_phdr; \
156 off = 0; \ 170 offset = 0; \
157 check_flags = PF_X; \ 171 check_flags = PF_X; \
158 } else if (EGET(phdr[i].p_type) == PT_GNU_RELRO) { \ 172 } else if (EGET(phdr[i].p_type) == PT_GNU_RELRO) { \
159 if (multi_relro++) warnf("%s: multiple PT_GNU_RELRO's !?", elf->filename); \ 173 if (multi_relro++) warnf("%s: multiple PT_GNU_RELRO's !?", elf->filename); \
160 found = found_relro; \ 174 found = found_relro; \
161 off = 4; \ 175 offset = 4; \
162 check_flags = PF_X; \ 176 check_flags = PF_X; \
163 } else if (EGET(phdr[i].p_type) == PT_LOAD) { \ 177 } else if (EGET(phdr[i].p_type) == PT_LOAD) { \
164 if (multi_load++ > 2) warnf("%s: more than 2 PT_LOAD's !?", elf->filename); \ 178 if (multi_load++ > 2) warnf("%s: more than 2 PT_LOAD's !?", elf->filename); \
165 found = found_load; \ 179 found = found_load; \
166 off = 8; \ 180 offset = 8; \
167 check_flags = PF_W|PF_X; \ 181 check_flags = PF_W|PF_X; \
168 } else \ 182 } else \
169 continue; \ 183 continue; \
170 flags = EGET(phdr[i].p_flags); \ 184 flags = EGET(phdr[i].p_flags); \
171 if (be_quiet && ((flags & check_flags) != check_flags)) \ 185 if (be_quiet && ((flags & check_flags) != check_flags)) \
172 continue; \ 186 continue; \
173 memcpy(ret+off, gnu_short_stack_flags(flags), 3); \ 187 memcpy(ret+offset, gnu_short_stack_flags(flags), 3); \
174 *found = 1; \ 188 *found = 1; \
175 ++shown; \ 189 ++shown; \
190 } \
191 } else if (elf->shdr != NULL) { \
192 /* no program headers which means this is prob an object file */ \
193 Elf ## B ## _Shdr *shdr = SHDR ## B (elf->shdr); \
194 Elf ## B ## _Shdr *strtbl = shdr + EGET(ehdr->e_shstrndx); \
195 check_flags = SHF_WRITE|SHF_EXECINSTR; \
196 for (i = 0; i < EGET(ehdr->e_shnum); ++i) { \
197 if (EGET(shdr[i].sh_type) != SHT_PROGBITS) continue; \
198 offset = EGET(strtbl->sh_offset) + EGET(shdr[i].sh_name); \
199 if (!strcmp((char*)(elf->data + offset), ".note.GNU-stack")) { \
200 if (multi_stack++) warnf("%s: multiple .note.GNU-stack's !?", elf->filename); \
201 flags = EGET(shdr[i].sh_flags); \
202 if (be_quiet && ((flags & check_flags) != check_flags)) \
203 continue; \
204 ++*found_phdr; \
205 shown = 1; \
206 if (flags & SHF_WRITE) ret[0] = 'W'; \
207 if (flags & SHF_ALLOC) ret[1] = 'A'; \
208 if (flags & SHF_EXECINSTR) ret[2] = 'X'; \
209 if (flags & 0xFFFFFFF8) warn("Invalid section flags for GNU-stack"); \
210 break; \
211 } \
212 } \
213 if (!multi_stack) { \
214 *found_phdr = 1; \
215 shown = 1; \
216 memcpy(ret, "!WX", 3); \
217 } \
176 } \ 218 } \
177 } 219 }
178 SHOW_PHDR(32) 220 SHOW_PHDR(32)
179 SHOW_PHDR(64) 221 SHOW_PHDR(64)
180 }
181 222
182 if (be_quiet && !shown) 223 if (be_wewy_wewy_quiet || (be_quiet && !shown))
183 return NULL; 224 return NULL;
184 else 225 else
185 return ret; 226 return ret;
186} 227}
187static char *scanelf_file_textrel(elfobj *elf, char *found_textrel) 228static const char *scanelf_file_textrel(elfobj *elf, char *found_textrel)
188{ 229{
189 static char ret[] = "TEXTREL"; 230 static const char *ret = "TEXTREL";
190 unsigned long i; 231 unsigned long i;
191 232
192 if (!show_textrel) return NULL; 233 if (!show_textrel && !show_textrels) return NULL;
193 234
194 if (elf->phdr) { 235 if (elf->phdr) {
195#define SHOW_TEXTREL(B) \ 236#define SHOW_TEXTREL(B) \
196 if (elf->elf_class == ELFCLASS ## B) { \ 237 if (elf->elf_class == ELFCLASS ## B) { \
197 Elf ## B ## _Dyn *dyn; \ 238 Elf ## B ## _Dyn *dyn; \
217 } 258 }
218 259
219 if (be_quiet || be_wewy_wewy_quiet) 260 if (be_quiet || be_wewy_wewy_quiet)
220 return NULL; 261 return NULL;
221 else 262 else
222 return (char *)" - "; 263 return " - ";
223} 264}
224static char *scanelf_file_textrels(elfobj *elf, char *found_textrels) 265static char *scanelf_file_textrels(elfobj *elf, char *found_textrels, char *found_textrel)
225{ 266{
226 /* To locate TEXTREL symbols:
227 * for each shdr of type SHT_REL:
228 * for each phdr of type PT_LOAD:
229 * if phdr is not writable (why?)
230 * if shdr offset is inside of load (shdr->offset, phdr->{vaddr,memsz}
231 * look up shdr's symbol name
232 */
233 unsigned long p, s, r, rmax; 267 unsigned long s, r, rmax;
234 char *symtab_void, *strtab_void; 268 void *symtab_void, *strtab_void, *text_void;
235 269
236 if (!show_textrels) return NULL; 270 if (!show_textrels) return NULL;
237 271
238 /* debug sections */ 272 /* don't search for TEXTREL's if the ELF doesn't have any */
239 symtab_void = elf_findsecbyname(elf, ".symtab"); 273 if (!*found_textrel) scanelf_file_textrel(elf, found_textrel);
274 if (!*found_textrel) return NULL;
275
276 scanelf_file_get_symtabs(elf, &symtab_void, &strtab_void);
240 strtab_void = elf_findsecbyname(elf, ".strtab"); 277 text_void = elf_findsecbyname(elf, ".text");
241 /* fall back to runtime sections */
242 if (!symtab_void || !strtab_void) {
243 symtab_void = elf_findsecbyname(elf, ".dynsym");
244 strtab_void = elf_findsecbyname(elf, ".dynstr");
245 }
246 278
247 if (elf->phdr && elf->shdr) { 279 if (symtab_void && strtab_void && text_void && elf->shdr) {
248#define SHOW_TEXTRELS(B) \ 280#define SHOW_TEXTRELS(B) \
249 if (elf->elf_class == ELFCLASS ## B) { \ 281 if (elf->elf_class == ELFCLASS ## B) { \
250 Elf ## B ## _Ehdr *ehdr = EHDR ## B (elf->ehdr); \ 282 Elf ## B ## _Ehdr *ehdr = EHDR ## B (elf->ehdr); \
251 Elf ## B ## _Phdr *phdr = PHDR ## B (elf->phdr); \
252 Elf ## B ## _Shdr *shdr = SHDR ## B (elf->shdr); \ 283 Elf ## B ## _Shdr *shdr = SHDR ## B (elf->shdr); \
253 Elf ## B ## _Shdr *symtab = SHDR ## B (symtab_void); \ 284 Elf ## B ## _Shdr *symtab = SHDR ## B (symtab_void); \
254 Elf ## B ## _Shdr *strtab = SHDR ## B (strtab_void); \ 285 Elf ## B ## _Shdr *strtab = SHDR ## B (strtab_void); \
286 Elf ## B ## _Shdr *text = SHDR ## B (text_void); \
287 Elf ## B ## _Addr vaddr = EGET(text->sh_addr); \
288 uint ## B ## _t memsz = EGET(text->sh_size); \
255 Elf ## B ## _Rel *rel; \ 289 Elf ## B ## _Rel *rel; \
256 Elf ## B ## _Rela *rela; \ 290 Elf ## B ## _Rela *rela; \
257 /* search the section headers for relocations */ \ 291 /* search the section headers for relocations */ \
258 for (s = 0; s < EGET(ehdr->e_shnum); ++s) { \ 292 for (s = 0; s < EGET(ehdr->e_shnum); ++s) { \
259 uint32_t sh_type = EGET(shdr[s].sh_type); \ 293 uint32_t sh_type = EGET(shdr[s].sh_type); \
265 rel = NULL; \ 299 rel = NULL; \
266 rela = RELA ## B (elf->data + EGET(shdr[s].sh_offset)); \ 300 rela = RELA ## B (elf->data + EGET(shdr[s].sh_offset)); \
267 rmax = EGET(shdr[s].sh_size) / sizeof(*rela); \ 301 rmax = EGET(shdr[s].sh_size) / sizeof(*rela); \
268 } else \ 302 } else \
269 continue; \ 303 continue; \
270 /* search the program headers for PT_LOAD headers */ \
271 for (p = 0; p < EGET(ehdr->e_phnum); ++p) { \
272 Elf ## B ## _Addr vaddr; \
273 uint ## B ## _t memsz; \
274 if (EGET(phdr[p].p_type) != PT_LOAD) continue; \
275 if (EGET(phdr[p].p_flags) & PF_W) continue; \
276 vaddr = EGET(phdr[p].p_vaddr); \
277 memsz = EGET(phdr[p].p_memsz); \
278 *found_textrels = 1; \
279 /* now see if any of the relocs are in the PT_LOAD */ \ 304 /* now see if any of the relocs are in the .text */ \
280 for (r = 0; r < rmax; ++r) { \ 305 for (r = 0; r < rmax; ++r) { \
281 unsigned long sym_max; \ 306 unsigned long sym_max; \
282 Elf ## B ## _Addr offset_tmp; \ 307 Elf ## B ## _Addr offset_tmp; \
283 Elf ## B ## _Sym *func; \ 308 Elf ## B ## _Sym *func; \
284 Elf ## B ## _Sym *sym; \ 309 Elf ## B ## _Sym *sym; \
285 Elf ## B ## _Addr r_offset; \ 310 Elf ## B ## _Addr r_offset; \
286 uint ## B ## _t r_info; \ 311 uint ## B ## _t r_info; \
287 if (sh_type == SHT_REL) { \ 312 if (sh_type == SHT_REL) { \
288 r_offset = EGET(rel[r].r_offset); \ 313 r_offset = EGET(rel[r].r_offset); \
289 r_info = EGET(rel[r].r_info); \ 314 r_info = EGET(rel[r].r_info); \
290 } else { \ 315 } else { \
291 r_offset = EGET(rela[r].r_offset); \ 316 r_offset = EGET(rela[r].r_offset); \
292 r_info = EGET(rela[r].r_info); \ 317 r_info = EGET(rela[r].r_info); \
318 } \
319 /* make sure this relocation is inside of the .text */ \
320 if (r_offset < vaddr || r_offset >= vaddr + memsz) { \
321 if (be_verbose <= 2) continue; \
322 } else \
323 *found_textrels = 1; \
324 /* locate this relocation symbol name */ \
325 sym = SYM ## B (elf->data + EGET(symtab->sh_offset)); \
326 sym_max = ELF ## B ## _R_SYM(r_info); \
327 if (sym_max * EGET(symtab->sh_entsize) < symtab->sh_size) \
328 sym += sym_max; \
329 else \
330 sym = NULL; \
331 sym_max = EGET(symtab->sh_size) / EGET(symtab->sh_entsize); \
332 /* show the raw details about this reloc */ \
333 printf(" %s: ", elf->base_filename); \
334 if (sym && sym->st_name) \
335 printf("%s", (char*)(elf->data + EGET(strtab->sh_offset) + EGET(sym->st_name))); \
336 else \
337 printf("(memory/fake?)"); \
338 printf(" [0x%lX]", (unsigned long)r_offset); \
339 /* now try to find the closest symbol that this rel is probably in */ \
340 sym = SYM ## B (elf->data + EGET(symtab->sh_offset)); \
341 func = NULL; \
342 offset_tmp = 0; \
343 while (sym_max--) { \
344 if (EGET(sym->st_value) < r_offset && EGET(sym->st_value) > offset_tmp) { \
345 func = sym; \
346 offset_tmp = EGET(sym->st_value); \
293 } \ 347 } \
294 /* make sure this relocation is inside of the .text */ \
295 if (r_offset < vaddr || r_offset >= vaddr + memsz) continue; \
296 sym_max = EGET(symtab->sh_size) / EGET(symtab->sh_entsize); \
297 /* locate this relocation symbol name */ \
298 sym = SYM ## B (elf->data + EGET(symtab->sh_offset)); \
299 sym += ELF ## B ## _R_SYM(r_info); \
300 /* show the raw details about this reloc */ \
301 printf("\tTEXTREL %s: ", elf->base_filename); \
302 if (sym->st_name) \
303 printf("%s", (char*)(elf->data + EGET(strtab->sh_offset) + EGET(sym->st_name))); \
304 else \
305 printf("(NULL: fake?)"); \
306 printf(" [0x%lX]", (unsigned long)r_offset); \
307 /* now try to find the closest symbol that this rel is probably in */ \
308 sym = SYM ## B (elf->data + EGET(symtab->sh_offset)); \
309 func = NULL; \
310 offset_tmp = 0; \
311 while (sym_max--) { \
312 if (EGET(sym->st_value) < r_offset && EGET(sym->st_value) > offset_tmp) { \
313 func = sym; \
314 offset_tmp = EGET(sym->st_value); \
315 } \
316 ++sym; \ 348 ++sym; \
317 } \
318 printf(" in "); \
319 if (func && func->st_name) \
320 printf("%s", (char*)(elf->data + EGET(strtab->sh_offset) + EGET(func->st_name))); \
321 else \
322 printf("(NULL: fake?)"); \
323 printf(" [0x%lX]\n", (unsigned long)offset_tmp); \
324 } \ 349 } \
350 printf(" in "); \
351 if (func && func->st_name) \
352 printf("%s", (char*)(elf->data + EGET(strtab->sh_offset) + EGET(func->st_name))); \
353 else \
354 printf("(NULL: fake?)"); \
355 printf(" [0x%lX]\n", (unsigned long)offset_tmp); \
325 } \ 356 } \
326 } } 357 } }
327 SHOW_TEXTRELS(32) 358 SHOW_TEXTRELS(32)
328 SHOW_TEXTRELS(64) 359 SHOW_TEXTRELS(64)
329 } 360 }
361 if (!*found_textrels)
362 warnf("ELF %s has TEXTREL markings but doesnt appear to have any real TEXTREL's !?", elf->filename);
330 363
331 return NULL; 364 return NULL;
365}
366
367static void rpath_security_checks(elfobj *, char *);
368static void rpath_security_checks(elfobj *elf, char *item) {
369 struct stat st;
370 switch (*item) {
371 case '/': break;
372 case '.':
373 warnf("Security problem with relative RPATH '%s' in %s", item, elf->filename);
374 break;
375 case '\0':
376 warnf("Security problem NULL RPATH in %s", elf->filename);
377 break;
378 case '$':
379 if (fstat(elf->fd, &st) != -1)
380 if ((st.st_mode & S_ISUID) || (st.st_mode & S_ISGID))
381 warnf("Security problem with RPATH='%s' in %s with mode set of %o",
382 item, elf->filename, st.st_mode & 07777);
383 break;
384 default:
385 warnf("Maybe? sec problem with RPATH='%s' in %s", item, elf->filename);
386 break;
387 }
332} 388}
333static void scanelf_file_rpath(elfobj *elf, char *found_rpath, char **ret, size_t *ret_len) 389static void scanelf_file_rpath(elfobj *elf, char *found_rpath, char **ret, size_t *ret_len)
334{ 390{
335 unsigned long i, s; 391 unsigned long i, s;
336 char *rpath, *runpath, **r; 392 char *rpath, *runpath, **r;
379 /* note that we only 'chop' off leading known paths. */ \ 435 /* note that we only 'chop' off leading known paths. */ \
380 /* since *r is read-only memory, we can only move the ptr forward. */ \ 436 /* since *r is read-only memory, we can only move the ptr forward. */ \
381 start = *r; \ 437 start = *r; \
382 /* scan each path in : delimited list */ \ 438 /* scan each path in : delimited list */ \
383 while (start) { \ 439 while (start) { \
440 rpath_security_checks(elf, start); \
384 end = strchr(start, ':'); \ 441 end = strchr(start, ':'); \
385 len = (end ? abs(end - start) : strlen(start)); \ 442 len = (end ? abs(end - start) : strlen(start)); \
386 for (s = 0; ldpaths[s]; ++s) { \ 443 for (s = 0; ldpaths[s]; ++s) { \
387 if (!strncmp(ldpaths[s], start, len) && !ldpaths[s][len]) { \ 444 if (!strncmp(ldpaths[s], start, len) && !ldpaths[s][len]) { \
388 *r = (end ? end + 1 : NULL); \ 445 *r = (end ? end + 1 : NULL); \
420 } else if (rpath || runpath) 477 } else if (rpath || runpath)
421 xstrcat(ret, (runpath ? runpath : rpath), ret_len); 478 xstrcat(ret, (runpath ? runpath : rpath), ret_len);
422 else if (!be_quiet) 479 else if (!be_quiet)
423 xstrcat(ret, " - ", ret_len); 480 xstrcat(ret, " - ", ret_len);
424} 481}
425static char *scanelf_file_needed_lib(elfobj *elf, char *found_needed, char *found_lib, int op, char **ret, size_t *ret_len) 482static const char *scanelf_file_needed_lib(elfobj *elf, char *found_needed, char *found_lib, int op, char **ret, size_t *ret_len)
426{ 483{
427 unsigned long i; 484 unsigned long i;
428 char *needed; 485 char *needed;
429 void *strtbl_void; 486 void *strtbl_void;
430 487
458 if (*found_needed) xchrcat(ret, ',', ret_len); \ 515 if (*found_needed) xchrcat(ret, ',', ret_len); \
459 xstrcat(ret, needed, ret_len); \ 516 xstrcat(ret, needed, ret_len); \
460 } \ 517 } \
461 *found_needed = 1; \ 518 *found_needed = 1; \
462 } else { \ 519 } else { \
463 if (strcmp(find_lib, needed)) return NULL; \ 520 if (!strncmp(find_lib, needed, strlen( !gmatch ? needed : find_lib))) { \
464 *found_lib = 1; \ 521 *found_lib = 1; \
465 return (be_wewy_wewy_quiet ? NULL : find_lib); \ 522 return (be_wewy_wewy_quiet ? NULL : needed); \
523 } \
466 } \ 524 } \
467 } \ 525 } \
468 ++dyn; \ 526 ++dyn; \
469 } \ 527 } \
470 } } 528 } }
471 SHOW_NEEDED(32) 529 SHOW_NEEDED(32)
472 SHOW_NEEDED(64) 530 SHOW_NEEDED(64)
531 if (op == 0 && !*found_needed && be_verbose)
532 warn("ELF lacks DT_NEEDED sections: %s", elf->filename);
473 } 533 }
474 534
475 return NULL; 535 return NULL;
476} 536}
477static char *scanelf_file_interp(elfobj *elf, char *found_interp) 537static char *scanelf_file_interp(elfobj *elf, char *found_interp)
535 } else { 595 } else {
536 *found_bind = 1; 596 *found_bind = 1;
537 return (char *) "LAZY"; 597 return (char *) "LAZY";
538 } 598 }
539} 599}
600static char *scanelf_file_soname(elfobj *elf, char *found_soname)
601{
602 unsigned long i;
603 char *soname;
604 void *strtbl_void;
605
606 if (!show_soname) return NULL;
607
608 strtbl_void = elf_findsecbyname(elf, ".dynstr");
609
610 if (elf->phdr && strtbl_void) {
611#define SHOW_SONAME(B) \
612 if (elf->elf_class == ELFCLASS ## B) { \
613 Elf ## B ## _Dyn *dyn; \
614 Elf ## B ## _Ehdr *ehdr = EHDR ## B (elf->ehdr); \
615 Elf ## B ## _Phdr *phdr = PHDR ## B (elf->phdr); \
616 Elf ## B ## _Shdr *strtbl = SHDR ## B (strtbl_void); \
617 Elf ## B ## _Off offset; \
618 /* only look for soname in shared objects */ \
619 if (ehdr->e_type != ET_DYN) \
620 return NULL; \
621 for (i = 0; i < EGET(ehdr->e_phnum); i++) { \
622 if (EGET(phdr[i].p_type) != PT_DYNAMIC) continue; \
623 offset = EGET(phdr[i].p_offset); \
624 if (offset >= elf->len - sizeof(Elf ## B ## _Dyn)) continue; \
625 dyn = DYN ## B (elf->data + offset); \
626 while (EGET(dyn->d_tag) != DT_NULL) { \
627 if (EGET(dyn->d_tag) == DT_SONAME) { \
628 offset = EGET(strtbl->sh_offset) + EGET(dyn->d_un.d_ptr); \
629 if (offset >= (Elf ## B ## _Off)elf->len) { \
630 ++dyn; \
631 continue; \
632 } \
633 soname = (char*)(elf->data + offset); \
634 *found_soname = 1; \
635 return (be_wewy_wewy_quiet ? NULL : soname); \
636 } \
637 ++dyn; \
638 } \
639 } }
640 SHOW_SONAME(32)
641 SHOW_SONAME(64)
642 }
643
644 return NULL;
645}
540static char *scanelf_file_sym(elfobj *elf, char *found_sym) 646static char *scanelf_file_sym(elfobj *elf, char *found_sym)
541{ 647{
542 unsigned long i; 648 unsigned long i;
543 void *symtab_void, *strtab_void; 649 void *symtab_void, *strtab_void;
544 650
545 if (!find_sym) return NULL; 651 if (!find_sym) return NULL;
546 652
547 /* debug sections */ 653 scanelf_file_get_symtabs(elf, &symtab_void, &strtab_void);
548 symtab_void = elf_findsecbyname(elf, ".symtab");
549 strtab_void = elf_findsecbyname(elf, ".strtab");
550 /* fall back to runtime sections */
551 if (!symtab_void || !strtab_void) {
552 symtab_void = elf_findsecbyname(elf, ".dynsym");
553 strtab_void = elf_findsecbyname(elf, ".dynstr");
554 }
555 654
556 if (symtab_void && strtab_void) { 655 if (symtab_void && strtab_void) {
557#define FIND_SYM(B) \ 656#define FIND_SYM(B) \
558 if (elf->elf_class == ELFCLASS ## B) { \ 657 if (elf->elf_class == ELFCLASS ## B) { \
559 Elf ## B ## _Shdr *symtab = SHDR ## B (symtab_void); \ 658 Elf ## B ## _Shdr *symtab = SHDR ## B (symtab_void); \
595#define prints(str) write(fileno(stdout), str, strlen(str)) 694#define prints(str) write(fileno(stdout), str, strlen(str))
596static void scanelf_file(const char *filename) 695static void scanelf_file(const char *filename)
597{ 696{
598 unsigned long i; 697 unsigned long i;
599 char found_pax, found_phdr, found_relro, found_load, found_textrel, 698 char found_pax, found_phdr, found_relro, found_load, found_textrel,
600 found_rpath, found_needed, found_interp, found_bind, 699 found_rpath, found_needed, found_interp, found_bind, found_soname,
601 found_sym, found_lib, found_file, found_textrels; 700 found_sym, found_lib, found_file, found_textrels;
602 elfobj *elf; 701 elfobj *elf;
603 struct stat st; 702 struct stat st;
604 static char *out_buffer = NULL; 703 static char *out_buffer = NULL;
605 static size_t out_len; 704 static size_t out_len;
618 if (be_verbose > 2) printf("%s: skipping non-file\n", filename); 717 if (be_verbose > 2) printf("%s: skipping non-file\n", filename);
619 return; 718 return;
620 } 719 }
621 720
622 found_pax = found_phdr = found_relro = found_load = found_textrel = \ 721 found_pax = found_phdr = found_relro = found_load = found_textrel = \
623 found_rpath = found_needed = found_interp = found_bind = \ 722 found_rpath = found_needed = found_interp = found_bind = found_soname = \
624 found_sym = found_lib = found_file = found_textrels = 0; 723 found_sym = found_lib = found_file = found_textrels = 0;
625 724
626 /* verify this is real ELF */ 725 /* verify this is real ELF */
627 if ((elf = readelf(filename)) == NULL) { 726 if ((elf = readelf(filename)) == NULL) {
628 if (be_verbose > 2) printf("%s: not an ELF\n", filename); 727 if (be_verbose > 2) printf("%s: not an ELF\n", filename);
660 case 't': prints("TEXTREL "); break; 759 case 't': prints("TEXTREL "); break;
661 case 'r': prints("RPATH "); break; 760 case 'r': prints("RPATH "); break;
662 case 'n': prints("NEEDED "); break; 761 case 'n': prints("NEEDED "); break;
663 case 'i': prints("INTERP "); break; 762 case 'i': prints("INTERP "); break;
664 case 'b': prints("BIND "); break; 763 case 'b': prints("BIND "); break;
764 case 'S': prints("SONAME "); break;
665 case 's': prints("SYM "); break; 765 case 's': prints("SYM "); break;
666 case 'N': prints("LIB "); break; 766 case 'N': prints("LIB "); break;
667 case 'T': prints("TEXTRELS "); break; 767 case 'T': prints("TEXTRELS "); break;
668 default: warnf("'%c' has no title ?", out_format[i]); 768 default: warnf("'%c' has no title ?", out_format[i]);
669 } 769 }
722 break; 822 break;
723 case 'o': out = get_elfetype(elf); break; 823 case 'o': out = get_elfetype(elf); break;
724 case 'x': out = scanelf_file_pax(elf, &found_pax); break; 824 case 'x': out = scanelf_file_pax(elf, &found_pax); break;
725 case 'e': out = scanelf_file_phdr(elf, &found_phdr, &found_relro, &found_load); break; 825 case 'e': out = scanelf_file_phdr(elf, &found_phdr, &found_relro, &found_load); break;
726 case 't': out = scanelf_file_textrel(elf, &found_textrel); break; 826 case 't': out = scanelf_file_textrel(elf, &found_textrel); break;
727 case 'T': out = scanelf_file_textrels(elf, &found_textrels); break; 827 case 'T': out = scanelf_file_textrels(elf, &found_textrels, &found_textrel); break;
728 case 'r': scanelf_file_rpath(elf, &found_rpath, &out_buffer, &out_len); break; 828 case 'r': scanelf_file_rpath(elf, &found_rpath, &out_buffer, &out_len); break;
729 case 'n': 829 case 'n':
730 case 'N': out = scanelf_file_needed_lib(elf, &found_needed, &found_lib, (out_format[i]=='N'), &out_buffer, &out_len); break; 830 case 'N': out = scanelf_file_needed_lib(elf, &found_needed, &found_lib, (out_format[i]=='N'), &out_buffer, &out_len); break;
731 case 'i': out = scanelf_file_interp(elf, &found_interp); break; 831 case 'i': out = scanelf_file_interp(elf, &found_interp); break;
732 case 'b': out = scanelf_file_bind(elf, &found_bind); break; 832 case 'b': out = scanelf_file_bind(elf, &found_bind); break;
833 case 'S': out = scanelf_file_soname(elf, &found_soname); break;
733 case 's': out = scanelf_file_sym(elf, &found_sym); break; 834 case 's': out = scanelf_file_sym(elf, &found_sym); break;
734 default: warnf("'%c' has no scan code?", out_format[i]); 835 default: warnf("'%c' has no scan code?", out_format[i]);
735 } 836 }
736 if (out) xstrcat(&out_buffer, out, &out_len); 837 if (out) xstrcat(&out_buffer, out, &out_len);
737 } 838 }
738 839
739#define FOUND_SOMETHING() \ 840#define FOUND_SOMETHING() \
740 (found_pax || found_phdr || found_relro || found_load || found_textrel || \ 841 (found_pax || found_phdr || found_relro || found_load || found_textrel || \
741 found_rpath || found_needed || found_interp || found_bind || \ 842 found_rpath || found_needed || found_interp || found_bind || \
742 found_sym || found_lib || found_textrels) 843 found_soname || found_sym || found_lib || found_textrels)
743 844
744 if (!found_file && (!be_quiet || (be_quiet && FOUND_SOMETHING()))) { 845 if (!found_file && (!be_quiet || (be_quiet && FOUND_SOMETHING()))) {
745 xchrcat(&out_buffer, ' ', &out_len); 846 xchrcat(&out_buffer, ' ', &out_len);
746 xstrcat(&out_buffer, filename, &out_len); 847 xstrcat(&out_buffer, filename, &out_len);
747 } 848 }
748 if (!be_quiet || (be_quiet && FOUND_SOMETHING())) 849 if (!be_quiet || (be_quiet && FOUND_SOMETHING())) {
749 puts(out_buffer); 850 puts(out_buffer);
851 fflush(stdout);
852 }
750 853
751 unreadelf(elf); 854 unreadelf(elf);
752} 855}
753 856
754/* scan a directory for ET_EXEC files and print when we find one */ 857/* scan a directory for ET_EXEC files and print when we find one */
896} 999}
897 1000
898 1001
899 1002
900/* usage / invocation handling functions */ 1003/* usage / invocation handling functions */
901#define PARSE_FLAGS "plRmyxetrnibs:N:TaqvF:f:o:BhV" 1004#define PARSE_FLAGS "plRmyxetrnibSs:gN:TaqvF:f:o:BhV"
902#define a_argument required_argument 1005#define a_argument required_argument
903static struct option const long_opts[] = { 1006static struct option const long_opts[] = {
904 {"path", no_argument, NULL, 'p'}, 1007 {"path", no_argument, NULL, 'p'},
905 {"ldpath", no_argument, NULL, 'l'}, 1008 {"ldpath", no_argument, NULL, 'l'},
906 {"recursive", no_argument, NULL, 'R'}, 1009 {"recursive", no_argument, NULL, 'R'},
911 {"textrel", no_argument, NULL, 't'}, 1014 {"textrel", no_argument, NULL, 't'},
912 {"rpath", no_argument, NULL, 'r'}, 1015 {"rpath", no_argument, NULL, 'r'},
913 {"needed", no_argument, NULL, 'n'}, 1016 {"needed", no_argument, NULL, 'n'},
914 {"interp", no_argument, NULL, 'i'}, 1017 {"interp", no_argument, NULL, 'i'},
915 {"bind", no_argument, NULL, 'b'}, 1018 {"bind", no_argument, NULL, 'b'},
1019 {"soname", no_argument, NULL, 'S'},
916 {"symbol", a_argument, NULL, 's'}, 1020 {"symbol", a_argument, NULL, 's'},
917 {"lib", a_argument, NULL, 'N'}, 1021 {"lib", a_argument, NULL, 'N'},
1022 {"gmatch", no_argument, NULL, 'g'},
918 {"textrels", no_argument, NULL, 'T'}, 1023 {"textrels", no_argument, NULL, 'T'},
919 {"all", no_argument, NULL, 'a'}, 1024 {"all", no_argument, NULL, 'a'},
920 {"quiet", no_argument, NULL, 'q'}, 1025 {"quiet", no_argument, NULL, 'q'},
921 {"verbose", no_argument, NULL, 'v'}, 1026 {"verbose", no_argument, NULL, 'v'},
922 {"format", a_argument, NULL, 'F'}, 1027 {"format", a_argument, NULL, 'F'},
939 "Print TEXTREL information", 1044 "Print TEXTREL information",
940 "Print RPATH information", 1045 "Print RPATH information",
941 "Print NEEDED information", 1046 "Print NEEDED information",
942 "Print INTERP information", 1047 "Print INTERP information",
943 "Print BIND information", 1048 "Print BIND information",
1049 "Print SONAME information",
944 "Find a specified symbol", 1050 "Find a specified symbol",
945 "Find a specified library", 1051 "Find a specified library",
1052 "Use strncmp to match libraries. (use with -N)",
946 "Locate cause of TEXTREL", 1053 "Locate cause of TEXTREL",
947 "Print all scanned info (-x -e -t -r -n -i -b)\n", 1054 "Print all scanned info (-x -e -t -r -b)\n",
948 "Only output 'bad' things", 1055 "Only output 'bad' things",
949 "Be verbose (can be specified more than once)", 1056 "Be verbose (can be specified more than once)",
950 "Use specified format for output", 1057 "Use specified format for output",
951 "Read input stream from a filename", 1058 "Read input stream from a filename",
952 "Write output stream to a filename", 1059 "Write output stream to a filename",
977 puts("\nThe format modifiers for the -F option are:"); 1084 puts("\nThe format modifiers for the -F option are:");
978 puts(" F Filename \tx PaX Flags \te STACK/RELRO"); 1085 puts(" F Filename \tx PaX Flags \te STACK/RELRO");
979 puts(" t TEXTREL \tr RPATH \tn NEEDED"); 1086 puts(" t TEXTREL \tr RPATH \tn NEEDED");
980 puts(" i INTERP \tb BIND \ts symbol"); 1087 puts(" i INTERP \tb BIND \ts symbol");
981 puts(" N library \to Type \tT TEXTRELs"); 1088 puts(" N library \to Type \tT TEXTRELs");
1089 puts(" S SONAME");
982 puts(" p filename (with search path removed)"); 1090 puts(" p filename (with search path removed)");
983 puts(" f base filename"); 1091 puts(" f filename (short name/basename)");
984 puts("Prefix each modifier with '%' (verbose) or '#' (silent)"); 1092 puts("Prefix each modifier with '%' (verbose) or '#' (silent)");
985 1093
986 exit(status); 1094 exit(status);
987} 1095}
988 1096
995 opterr = 0; 1103 opterr = 0;
996 while ((i=getopt_long(argc, argv, PARSE_FLAGS, long_opts, NULL)) != -1) { 1104 while ((i=getopt_long(argc, argv, PARSE_FLAGS, long_opts, NULL)) != -1) {
997 switch (i) { 1105 switch (i) {
998 1106
999 case 'V': 1107 case 'V':
1000 printf("%s compiled %s\n%s\n" 1108 printf("pax-utils-%s: %s compiled %s\n%s\n"
1001 "%s written for Gentoo Linux by <solar and vapier @ gentoo.org>\n", 1109 "%s written for Gentoo by <solar and vapier @ gentoo.org>\n",
1002 __FILE__, __DATE__, rcsid, argv0); 1110 VERSION, __FILE__, __DATE__, rcsid, argv0);
1003 exit(EXIT_SUCCESS); 1111 exit(EXIT_SUCCESS);
1004 break; 1112 break;
1005 case 'h': usage(EXIT_SUCCESS); break; 1113 case 'h': usage(EXIT_SUCCESS); break;
1006 case 'f': 1114 case 'f':
1007 if (from_file) err("Don't specify -f twice"); 1115 if (from_file) err("Don't specify -f twice");
1034 if (out_format) err("Don't specify -F twice"); 1142 if (out_format) err("Don't specify -F twice");
1035 out_format = xstrdup(optarg); 1143 out_format = xstrdup(optarg);
1036 break; 1144 break;
1037 } 1145 }
1038 1146
1147 case 'g': gmatch = 1;
1039 case 'y': scan_symlink = 0; break; 1148 case 'y': scan_symlink = 0; break;
1040 case 'B': show_banner = 0; break; 1149 case 'B': show_banner = 0; break;
1041 case 'l': scan_ldpath = 1; break; 1150 case 'l': scan_ldpath = 1; break;
1042 case 'p': scan_envpath = 1; break; 1151 case 'p': scan_envpath = 1; break;
1043 case 'R': dir_recurse = 1; break; 1152 case 'R': dir_recurse = 1; break;
1047 case 't': show_textrel = 1; break; 1156 case 't': show_textrel = 1; break;
1048 case 'r': show_rpath = 1; break; 1157 case 'r': show_rpath = 1; break;
1049 case 'n': show_needed = 1; break; 1158 case 'n': show_needed = 1; break;
1050 case 'i': show_interp = 1; break; 1159 case 'i': show_interp = 1; break;
1051 case 'b': show_bind = 1; break; 1160 case 'b': show_bind = 1; break;
1161 case 'S': show_soname = 1; break;
1052 case 'T': show_textrels = 1; break; 1162 case 'T': show_textrels = 1; break;
1053 case 'q': be_quiet = 1; break; 1163 case 'q': be_quiet = 1; break;
1054 case 'v': be_verbose = (be_verbose % 20) + 1; break; 1164 case 'v': be_verbose = (be_verbose % 20) + 1; break;
1055 case 'a': show_pax = show_phdr = show_textrel = show_rpath = \ 1165 case 'a': show_pax = show_phdr = show_textrel = show_rpath = show_bind = 1; break;
1056 show_needed = show_interp = show_bind = 1; break;
1057 1166
1058 case ':': 1167 case ':':
1059 err("Option missing parameter\n"); 1168 err("Option missing parameter\n");
1060 case '?': 1169 case '?':
1061 err("Unknown option\n"); 1170 err("Unknown option\n");
1065 } 1174 }
1066 1175
1067 /* let the format option override all other options */ 1176 /* let the format option override all other options */
1068 if (out_format) { 1177 if (out_format) {
1069 show_pax = show_phdr = show_textrel = show_rpath = \ 1178 show_pax = show_phdr = show_textrel = show_rpath = \
1070 show_needed = show_interp = show_bind = show_textrels = 0; 1179 show_needed = show_interp = show_bind = show_soname = \
1180 show_textrels = 0;
1071 for (i = 0; out_format[i]; ++i) { 1181 for (i = 0; out_format[i]; ++i) {
1072 if (!IS_MODIFIER(out_format[i])) continue; 1182 if (!IS_MODIFIER(out_format[i])) continue;
1073 1183
1074 switch (out_format[++i]) { 1184 switch (out_format[++i]) {
1075 case '%': break; 1185 case '%': break;
1085 case 't': show_textrel = 1; break; 1195 case 't': show_textrel = 1; break;
1086 case 'r': show_rpath = 1; break; 1196 case 'r': show_rpath = 1; break;
1087 case 'n': show_needed = 1; break; 1197 case 'n': show_needed = 1; break;
1088 case 'i': show_interp = 1; break; 1198 case 'i': show_interp = 1; break;
1089 case 'b': show_bind = 1; break; 1199 case 'b': show_bind = 1; break;
1200 case 'S': show_soname = 1; break;
1090 case 'T': show_textrels = 1; break; 1201 case 'T': show_textrels = 1; break;
1091 default: 1202 default:
1092 err("Invalid format specifier '%c' (byte %i)", 1203 err("Invalid format specifier '%c' (byte %i)",
1093 out_format[i], i+1); 1204 out_format[i], i+1);
1094 } 1205 }
1104 if (show_textrel) xstrcat(&out_format, "%t ", &fmt_len); 1215 if (show_textrel) xstrcat(&out_format, "%t ", &fmt_len);
1105 if (show_rpath) xstrcat(&out_format, "%r ", &fmt_len); 1216 if (show_rpath) xstrcat(&out_format, "%r ", &fmt_len);
1106 if (show_needed) xstrcat(&out_format, "%n ", &fmt_len); 1217 if (show_needed) xstrcat(&out_format, "%n ", &fmt_len);
1107 if (show_interp) xstrcat(&out_format, "%i ", &fmt_len); 1218 if (show_interp) xstrcat(&out_format, "%i ", &fmt_len);
1108 if (show_bind) xstrcat(&out_format, "%b ", &fmt_len); 1219 if (show_bind) xstrcat(&out_format, "%b ", &fmt_len);
1220 if (show_soname) xstrcat(&out_format, "%S ", &fmt_len);
1109 if (show_textrels) xstrcat(&out_format, "%T ", &fmt_len); 1221 if (show_textrels) xstrcat(&out_format, "%T ", &fmt_len);
1110 if (find_sym) xstrcat(&out_format, "%s ", &fmt_len); 1222 if (find_sym) xstrcat(&out_format, "%s ", &fmt_len);
1111 if (find_lib) xstrcat(&out_format, "%N ", &fmt_len); 1223 if (find_lib) xstrcat(&out_format, "%N ", &fmt_len);
1112 if (!be_quiet) xstrcat(&out_format, "%F ", &fmt_len); 1224 if (!be_quiet) xstrcat(&out_format, "%F ", &fmt_len);
1113 } 1225 }

Legend:
Removed from v.1.76  
changed lines
  Added in v.1.91

  ViewVC Help
Powered by ViewVC 1.1.20