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