1 | /* $Id: emxomfstrip.c 3940 2014-11-10 20:16:06Z bird $ */
|
---|
2 | /** @file
|
---|
3 | * emxstrip - Simple LX debug info stripping tool.
|
---|
4 | *
|
---|
5 | * @copyright Copyright (C) 2014 knut st. osmundsen <bird-klibc-spam-xiv@anduin.net>
|
---|
6 | * @licenses MIT, BSD2, BSD3, BSD4, LGPLv2.1, LGPLv3, LGPLvFuture.
|
---|
7 | */
|
---|
8 |
|
---|
9 |
|
---|
10 | /*******************************************************************************
|
---|
11 | * Header Files *
|
---|
12 | *******************************************************************************/
|
---|
13 | #include "defs.h"
|
---|
14 | #include <errno.h>
|
---|
15 | #include <getopt.h>
|
---|
16 | #include <stdarg.h>
|
---|
17 | #include <stdio.h>
|
---|
18 | #include <stdlib.h>
|
---|
19 | #include <string.h>
|
---|
20 | #include <sys/stat.h>
|
---|
21 | #include <sys/omflib.h>
|
---|
22 | #if defined(_MSC_VER) || defined(__WATCOMC__)
|
---|
23 | # include <io.h>
|
---|
24 | # define ftruncate _chsize
|
---|
25 | #endif
|
---|
26 |
|
---|
27 |
|
---|
28 |
|
---|
29 | /*******************************************************************************
|
---|
30 | * Structures and Typedefs *
|
---|
31 | *******************************************************************************/
|
---|
32 | typedef struct EMXOMFSTRIPOPTS
|
---|
33 | {
|
---|
34 | const char *pszInFile;
|
---|
35 | const char *pszDbgFile;
|
---|
36 | const char *pszOutFile;
|
---|
37 | int cVerbose;
|
---|
38 | } EMXOMFSTRIPOPTS;
|
---|
39 |
|
---|
40 |
|
---|
41 | static int error(const char *pszMsg, ...)
|
---|
42 | {
|
---|
43 | va_list va;
|
---|
44 | va_start(va, pszMsg);
|
---|
45 | fprintf(stderr, "emxomfstrip: error: ");
|
---|
46 | vfprintf(stderr, pszMsg, va);
|
---|
47 | va_end(va);
|
---|
48 | return 1;
|
---|
49 | }
|
---|
50 |
|
---|
51 |
|
---|
52 | static int copyFileBytes(FILE *pDst, const char *pszDstName, FILE *pSrc, const char *pszSrcName,
|
---|
53 | unsigned long offSrc, unsigned long cb)
|
---|
54 | {
|
---|
55 | if (fseek(pDst, 0, SEEK_SET) != 0)
|
---|
56 | return error("Seek error: %s\n", strerror(errno));
|
---|
57 | if (fseek(pSrc, offSrc, SEEK_SET) != 0)
|
---|
58 | return error("Seek error: %s\n", strerror(errno));
|
---|
59 | while (cb > 0)
|
---|
60 | {
|
---|
61 | byte abBuf[0x8000];
|
---|
62 | unsigned cbThis = cb < sizeof(abBuf) ? (unsigned)cb : sizeof(abBuf);
|
---|
63 | if (fread(abBuf, cbThis, 1, pSrc) != 1)
|
---|
64 | return error("fread (@%#lx) failed on '%s': %s\n", offSrc, pszSrcName, strerror(errno));
|
---|
65 | if (fwrite(abBuf, cbThis, 1, pDst) != 1)
|
---|
66 | return error("fwrite failed on '%s': %s\n", pszDstName, strerror(errno));
|
---|
67 | cb -= cbThis;
|
---|
68 | offSrc += cbThis;
|
---|
69 | }
|
---|
70 | return 0;
|
---|
71 | }
|
---|
72 |
|
---|
73 |
|
---|
74 | static int isNbSignature(byte const *pbMagic)
|
---|
75 | {
|
---|
76 | return pbMagic[0] == 'N'
|
---|
77 | && pbMagic[1] == 'B'
|
---|
78 | && pbMagic[2] >= '0' && pbMagic[2] <= '1'
|
---|
79 | && pbMagic[3] >= '0' && pbMagic[3] <= '9';
|
---|
80 | }
|
---|
81 |
|
---|
82 |
|
---|
83 | static int isLxDebugInfoSignature(byte const *pbMagic)
|
---|
84 | {
|
---|
85 | return isNbSignature(pbMagic);
|
---|
86 | }
|
---|
87 |
|
---|
88 |
|
---|
89 | /**
|
---|
90 | * Strips a file.
|
---|
91 | *
|
---|
92 | * @returns 0 on success, 1 on failure, -1 if no debug info.
|
---|
93 | * @param pOpts Options.
|
---|
94 | * @param pInFile The input file.
|
---|
95 | * @param pOutFile The output file. Can be the same as @a pInFile, can
|
---|
96 | * also be NULL (no stripping).
|
---|
97 | * @param pDbgFile Where to write the raw debug.
|
---|
98 | */
|
---|
99 | static int stripFile(EMXOMFSTRIPOPTS *pOpts, FILE *pInFile, FILE *pOutFile, FILE *pDbgFile)
|
---|
100 | {
|
---|
101 | struct stat StIn;
|
---|
102 | unsigned long offLX = 0;
|
---|
103 | unsigned long offDbg = 0;
|
---|
104 | unsigned long cbDbg = 0;
|
---|
105 | int rc;
|
---|
106 | union
|
---|
107 | {
|
---|
108 | struct exe1p2_header mz;
|
---|
109 | struct os2_header lx;
|
---|
110 | byte abBuf[32];
|
---|
111 | } uHdr;
|
---|
112 | union
|
---|
113 | {
|
---|
114 | byte abBuf[32];
|
---|
115 | word awBuf[32/2];
|
---|
116 | dword adwBuf[32/4];
|
---|
117 | } uBuf;
|
---|
118 |
|
---|
119 | if (fstat(fileno(pInFile), &StIn) != 0)
|
---|
120 | return error("fstat failed on '%s': %s\n", pOpts->pszInFile, strerror(errno));
|
---|
121 |
|
---|
122 | /*
|
---|
123 | * Parse the executable header.
|
---|
124 | */
|
---|
125 | if (fread(&uHdr.mz, sizeof(uHdr.mz), 1, pInFile) != 1)
|
---|
126 | return error("Failed to read the header of '%s': %s\n", pOpts->pszInFile, strerror(errno));
|
---|
127 | if (uHdr.mz.e_magic == EXE_MAGIC_MZ)
|
---|
128 | {
|
---|
129 | offLX = uHdr.mz.e_lfanew;
|
---|
130 | if (fseek(pInFile, offLX, SEEK_SET) != 0)
|
---|
131 | return error("Failed to seek to 2nd header of '%s': %s\n", pOpts->pszInFile, strerror(errno));
|
---|
132 | if (fread(&uHdr.lx, sizeof(uHdr.lx), 1, pInFile) != 1)
|
---|
133 | return error("Failed to read the LX header of '%s': %s\n", pOpts->pszInFile, strerror(errno));
|
---|
134 | if (uHdr.lx.magic != EXE_MAGIC_LX)
|
---|
135 | return error("Header of '%s' is not recognized (%#x)\n", pOpts->pszInFile, uHdr.lx.magic);
|
---|
136 | }
|
---|
137 | else if (uHdr.lx.magic == EXE_MAGIC_LX)
|
---|
138 | {
|
---|
139 | if (fread(&uHdr.mz + 1, sizeof(uHdr.lx) - sizeof(uHdr.mz), 1, pInFile) != 1)
|
---|
140 | return error("Failed to read the header of '%s': %s\n", pOpts->pszInFile, strerror(errno));
|
---|
141 | }
|
---|
142 | else if ( uHdr.abBuf[0] == THEADR
|
---|
143 | && (uHdr.abBuf[1] > 3 || uHdr.abBuf[2]) )
|
---|
144 | return error("%s: Stripping OMF object files is not currently supported!\n", pOpts->pszInFile);
|
---|
145 | else if ( uHdr.abBuf[0] == LIBHDR
|
---|
146 | && (uHdr.abBuf[1] > 3 || uHdr.abBuf[2]) )
|
---|
147 | return error("%s: Stripping OMF library files is not currently supported!\n", pOpts->pszInFile);
|
---|
148 | else
|
---|
149 | return error("Header of '%s' is not recognized (%#x)\n", pOpts->pszInFile, uHdr.mz.e_magic);
|
---|
150 |
|
---|
151 | /*
|
---|
152 | * Is there a pointer to the debug info in the header? If so, check that
|
---|
153 | * it is at the end of the file.
|
---|
154 | */
|
---|
155 | if ( uHdr.lx.debug_offset != 0
|
---|
156 | && uHdr.lx.debug_size != 0 && 0)
|
---|
157 | {
|
---|
158 | offDbg = uHdr.lx.debug_offset;
|
---|
159 | cbDbg = uHdr.lx.debug_size;
|
---|
160 | if ( offDbg > StIn.st_size
|
---|
161 | || cbDbg > StIn.st_size
|
---|
162 | || (off_t)offDbg + cbDbg > StIn.st_size)
|
---|
163 | return error("Debug info indicated by the LX header of '%s' makes no sense! (%#lx + %#lx = %#lx (file: %#x)\n",
|
---|
164 | pOpts->pszInFile, offDbg, cbDbg, offDbg + cbDbg, (unsigned long)StIn.st_size);
|
---|
165 |
|
---|
166 | if ( fseek(pInFile, offDbg, SEEK_SET) != 0
|
---|
167 | || fread(&uBuf, 4, 1, pInFile) != 1)
|
---|
168 | return error("Error seeking/reading debug info header (@%#lx) in '%s': %s\n",
|
---|
169 | offDbg, pOpts->pszInFile, strerror(errno));
|
---|
170 | if (!isLxDebugInfoSignature(uBuf.abBuf))
|
---|
171 | {
|
---|
172 | /* Try add the LX offset. */
|
---|
173 | if ( fseek(pInFile, offDbg + offLX, SEEK_SET) == 0
|
---|
174 | && fread(&uBuf.abBuf[4], 4, 1, pInFile) == 0
|
---|
175 | && isLxDebugInfoSignature(&uBuf.abBuf[4]))
|
---|
176 | offDbg += offLX;
|
---|
177 | else
|
---|
178 | return error("Debug info indicated by the LX header of '%s' is not recognized! (magic: %02x %02x %02x %02x)\n",
|
---|
179 | pOpts->pszInFile, uBuf.abBuf[0], uBuf.abBuf[1], uBuf.abBuf[2], uBuf.abBuf[3]);
|
---|
180 | }
|
---|
181 |
|
---|
182 | if (offDbg + cbDbg != StIn.st_size) /* (This can be relaxed if we wish.) */
|
---|
183 | return error("Debug info indicated by the LX header is not at the end of '%s'! (%#lx + %#lx = %#lx (file: %#x)\n",
|
---|
184 | pOpts->pszInFile, offDbg, cbDbg, offDbg + cbDbg, (unsigned long)StIn.st_size);
|
---|
185 | }
|
---|
186 | /*
|
---|
187 | * Check for debug info at the end of the file.
|
---|
188 | */
|
---|
189 | else
|
---|
190 | {
|
---|
191 | if (fseek(pInFile, -8, SEEK_END) != 0)
|
---|
192 | return error("Failed seeking in '%s': %s\n", pOpts->pszInFile, strerror(errno));
|
---|
193 | if (fread(&uBuf, 8, 1, pInFile) != 1)
|
---|
194 | return error("Error reading last 8 bytes of '%s': %s\n", pOpts->pszInFile, strerror(errno));
|
---|
195 | if (isNbSignature(uBuf.abBuf))
|
---|
196 | {
|
---|
197 | int iVer = (uBuf.abBuf[2] - '0') * 10 + (uBuf.abBuf[3] - '0');
|
---|
198 | if (iVer != 4 && iVer != 2)
|
---|
199 | fprintf(stderr, "emxomfstrip: warning: Unknown debug info signature '%4.4s' in '%s'.\n",
|
---|
200 | uBuf.abBuf, pOpts->pszInFile);
|
---|
201 | cbDbg = uBuf.adwBuf[1];
|
---|
202 | if ( cbDbg >= (unsigned long)StIn.st_size
|
---|
203 | || cbDbg <= 16)
|
---|
204 | return error("Bad size in NB trailer debug of '%s': %#x (file size %#lx)\n",
|
---|
205 | pOpts->pszInFile, cbDbg, (unsigned long)StIn.st_size);
|
---|
206 | offDbg = (unsigned long)StIn.st_size - cbDbg;
|
---|
207 |
|
---|
208 | if ( fseek(pInFile, offDbg, SEEK_SET) != 0
|
---|
209 | || fread(&uBuf.abBuf[4], 4, 1, pInFile) != 1)
|
---|
210 | return error("Error seeking/reading debug info header (@%#lx) in '%s': %s\n",
|
---|
211 | offDbg, pOpts->pszInFile, strerror(errno));
|
---|
212 | if (uBuf.adwBuf[0] != uBuf.adwBuf[1])
|
---|
213 | return error("Debug header does not match trailer in '%s': %02x %02x %02x %02x != %02x %02x %02x %02x\n",
|
---|
214 | pOpts->pszInFile,
|
---|
215 | uBuf.abBuf[0], uBuf.abBuf[1], uBuf.abBuf[2], uBuf.abBuf[3],
|
---|
216 | uBuf.abBuf[4+0], uBuf.abBuf[4+1], uBuf.abBuf[4+2], uBuf.abBuf[4+3]);
|
---|
217 | }
|
---|
218 | }
|
---|
219 |
|
---|
220 | /*
|
---|
221 | * Did we find anything to strip? If not return immediately indicating this.
|
---|
222 | */
|
---|
223 | if (cbDbg == 0)
|
---|
224 | return -1;
|
---|
225 |
|
---|
226 | /*
|
---|
227 | * Do we copy the debug info into a separate file first?
|
---|
228 | */
|
---|
229 | rc = 0;
|
---|
230 | if (pDbgFile)
|
---|
231 | rc = copyFileBytes(pDbgFile, pOpts->pszDbgFile, pInFile, pOpts->pszInFile, offDbg, cbDbg);
|
---|
232 |
|
---|
233 | /*
|
---|
234 | * If pOutFile is NULL, we're only supposed to extract the debug info.
|
---|
235 | */
|
---|
236 | if (rc == 0 && pOutFile != NULL)
|
---|
237 | {
|
---|
238 | /*
|
---|
239 | * If there is a separate output file, copy over the bytes that we should keep.
|
---|
240 | */
|
---|
241 | if (pOutFile != pInFile)
|
---|
242 | rc = copyFileBytes(pOutFile, pOpts->pszOutFile, pInFile, pOpts->pszInFile, 0, offDbg);
|
---|
243 |
|
---|
244 | /*
|
---|
245 | * Update the LX header in the output(/input) file.
|
---|
246 | */
|
---|
247 | if ( rc == 0
|
---|
248 | && ( uHdr.lx.debug_offset != 0
|
---|
249 | || uHdr.lx.debug_size != 0))
|
---|
250 | {
|
---|
251 | uHdr.lx.debug_offset = 0;
|
---|
252 | uHdr.lx.debug_size = 0;
|
---|
253 | uHdr.lx.loader_checksum = 0;
|
---|
254 | if (fseek(pOutFile, offLX, SEEK_SET) != 0)
|
---|
255 | return error("Error seeking to LX header in output file: %s\n", strerror(errno));
|
---|
256 | if (fwrite(&uHdr.lx, sizeof(uHdr.lx), 1, pOutFile) != 1)
|
---|
257 | return error("Error writing LX header of output file: %s\n", strerror(errno));
|
---|
258 | }
|
---|
259 |
|
---|
260 | /*
|
---|
261 | * Truncate the output file if it's the same as the input.
|
---|
262 | */
|
---|
263 | if (rc == 0 && pOutFile == pInFile)
|
---|
264 | {
|
---|
265 | if (ftruncate(fileno(pOutFile), offDbg) != 0)
|
---|
266 | return error("Error truncating '%s': %s\n", pOpts->pszInFile, strerror(errno));
|
---|
267 | }
|
---|
268 | }
|
---|
269 |
|
---|
270 | return rc;
|
---|
271 | }
|
---|
272 |
|
---|
273 |
|
---|
274 | static int usage(FILE *pOut, int rc)
|
---|
275 | {
|
---|
276 | fprintf(pOut, "emxomfstrip [-dgsSxXvV] [-D dbginfo.file] [-o output.file] <file1> [file2...]\n");
|
---|
277 | return rc;
|
---|
278 | }
|
---|
279 |
|
---|
280 |
|
---|
281 | int main(int argc, char **argv)
|
---|
282 | {
|
---|
283 | int ch;
|
---|
284 | int rcExit = 0;
|
---|
285 | EMXOMFSTRIPOPTS Opts = { NULL, NULL, NULL, 0 } ;
|
---|
286 |
|
---|
287 | #ifdef __EMX__
|
---|
288 | /* Preload the executable. */
|
---|
289 | _emxload_env("GCCLOAD");
|
---|
290 | #endif
|
---|
291 |
|
---|
292 | /* Get more arguments from EMXOMFSTRIPOPT and load reponse files and expand wildcards. */
|
---|
293 | #ifdef __EMX__
|
---|
294 | _envargs(&argc, &argv, "EMXOMFSTRIPOPT");
|
---|
295 | _response(&argc, &argv);
|
---|
296 | _wildcard(&argc, &argv);
|
---|
297 | #endif
|
---|
298 |
|
---|
299 | /*
|
---|
300 | * Parse the options.
|
---|
301 | */
|
---|
302 | while ((ch = getopt_long(argc, argv, "dD:go:sSxXvV", NULL, NULL)) != EOF)
|
---|
303 | {
|
---|
304 | switch (ch)
|
---|
305 | {
|
---|
306 | case 'd':
|
---|
307 | case 'g':
|
---|
308 | case 's':
|
---|
309 | case 'S':
|
---|
310 | case 'x':
|
---|
311 | case 'X':
|
---|
312 | break;
|
---|
313 |
|
---|
314 | case 'D':
|
---|
315 | Opts.pszDbgFile = optarg;
|
---|
316 | break;
|
---|
317 |
|
---|
318 | case 'o':
|
---|
319 | Opts.pszOutFile = optarg;
|
---|
320 | break;
|
---|
321 |
|
---|
322 | case 'v':
|
---|
323 | Opts.cVerbose++;
|
---|
324 | break;
|
---|
325 |
|
---|
326 | case 'V':
|
---|
327 | printf(VERSION INNOTEK_VERSION "\n");
|
---|
328 | return 0;
|
---|
329 |
|
---|
330 | default:
|
---|
331 | return usage(stderr, 1);
|
---|
332 | }
|
---|
333 | }
|
---|
334 |
|
---|
335 | if (argc - optind == 0)
|
---|
336 | return usage(stderr, 1);
|
---|
337 | if ( argc - optind != 1
|
---|
338 | && (Opts.pszDbgFile || Opts.pszOutFile))
|
---|
339 | return error("exactly one file must be specified if -D or -o is used\n");
|
---|
340 |
|
---|
341 | /*
|
---|
342 | * Process the files.
|
---|
343 | */
|
---|
344 | while ( optind < argc
|
---|
345 | && rcExit == 0)
|
---|
346 | {
|
---|
347 | /* Open the input and maybe output file. */
|
---|
348 | FILE *pInFile;
|
---|
349 | Opts.pszInFile = argv[optind];
|
---|
350 | if (Opts.pszOutFile)
|
---|
351 | pInFile = fopen(Opts.pszInFile, "rb");
|
---|
352 | else
|
---|
353 | pInFile = fopen(Opts.pszInFile, "r+b");
|
---|
354 | if (pInFile)
|
---|
355 | {
|
---|
356 | /* Open the output file if it differs. Special handling of /dev/null
|
---|
357 | for facilitaing only extracting the debug info. */
|
---|
358 | int fNull = 0;
|
---|
359 | FILE *pOutFile;
|
---|
360 | if (!Opts.pszOutFile)
|
---|
361 | pOutFile = pInFile;
|
---|
362 | else
|
---|
363 | {
|
---|
364 | if (strcmp(Opts.pszOutFile, "/dev/null"))
|
---|
365 | pOutFile = fopen(Opts.pszOutFile, "wb");
|
---|
366 | else
|
---|
367 | {
|
---|
368 | fNull = 1;
|
---|
369 | pOutFile = NULL;
|
---|
370 | }
|
---|
371 | }
|
---|
372 | if (pOutFile || fNull)
|
---|
373 | {
|
---|
374 | /* Open the debug file. */
|
---|
375 | FILE *pDbgFile = NULL;
|
---|
376 | if (Opts.pszDbgFile)
|
---|
377 | pDbgFile = fopen(Opts.pszDbgFile, "wb");
|
---|
378 | if (pDbgFile || !Opts.pszDbgFile)
|
---|
379 | {
|
---|
380 | /*
|
---|
381 | * All the files have been opened, get down to business.
|
---|
382 | */
|
---|
383 | int rc = stripFile(&Opts, pInFile, pOutFile, pDbgFile);
|
---|
384 | if (rc != 0 && rc != -1)
|
---|
385 | rcExit = rc;
|
---|
386 |
|
---|
387 | /*
|
---|
388 | * Close the files. The user can clean up files on
|
---|
389 | * failure, at least for now.
|
---|
390 | */
|
---|
391 | if (pDbgFile && fclose(pDbgFile) != 0)
|
---|
392 | rcExit = error("fclose failed on '%s': %s\n", Opts.pszDbgFile, strerror(errno));
|
---|
393 | else if (rc == -1 && Opts.pszDbgFile != NULL && unlink(Opts.pszDbgFile) != 0)
|
---|
394 | rcExit = error("Failed to unlink debug info file '%s': %s\n", Opts.pszDbgFile, strerror(errno));
|
---|
395 | }
|
---|
396 | else
|
---|
397 | rcExit = error("Failed opening debug info file '%s': %s\n", Opts.pszDbgFile, strerror(errno));
|
---|
398 | if (pOutFile && pOutFile != pInFile && fclose(pOutFile) != 0)
|
---|
399 | rcExit = error("fclose failed on '%s': %s\n", Opts.pszOutFile, strerror(errno));
|
---|
400 | }
|
---|
401 | else
|
---|
402 | rcExit = error("Failed opening output file '%s': %s\n", Opts.pszOutFile, strerror(errno));
|
---|
403 | if (fclose(pInFile) != 0)
|
---|
404 | rcExit = error("fclose failed on '%s': %s\n", Opts.pszInFile, strerror(errno));
|
---|
405 | }
|
---|
406 | else
|
---|
407 | rcExit = error("Failed opening '%s': %s\n", Opts.pszInFile, strerror(errno));
|
---|
408 | optind++;
|
---|
409 | }
|
---|
410 |
|
---|
411 | return rcExit;
|
---|
412 | }
|
---|
413 |
|
---|