1/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
2 *
3 * ***** BEGIN LICENSE BLOCK *****
4 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 *
6 * The contents of this file are subject to the Mozilla Public License Version
7 * 1.1 (the "License"); you may not use this file except in compliance with
8 * the License. You may obtain a copy of the License at
9 * http://www.mozilla.org/MPL/
10 *
11 * Software distributed under the License is distributed on an "AS IS" basis,
12 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13 * for the specific language governing rights and limitations under the
14 * License.
15 *
16 * The Original Code is nm2tsv.c code, released
17 * Oct 10, 2002.
18 *
19 * The Initial Developer of the Original Code is
20 * Netscape Communications Corporation.
21 * Portions created by the Initial Developer are Copyright (C) 2002
22 * the Initial Developer. All Rights Reserved.
23 *
24 * Contributor(s):
25 *   Garrett Arch Blythe, 10-October-2002
26 *
27 * Alternatively, the contents of this file may be used under the terms of
28 * either the GNU General Public License Version 2 or later (the "GPL"), or
29 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
30 * in which case the provisions of the GPL or the LGPL are applicable instead
31 * of those above. If you wish to allow use of your version of this file only
32 * under the terms of either the GPL or the LGPL, and not to allow others to
33 * use your version of this file under the terms of the MPL, indicate your
34 * decision by deleting the provisions above and replace them with the notice
35 * and other provisions required by the GPL or the LGPL. If you do not delete
36 * the provisions above, a recipient may use your version of this file under
37 * the terms of any one of the MPL, the GPL or the LGPL.
38 *
39 * ***** END LICENSE BLOCK ***** */
40
41#include <stdio.h>
42#include <stdlib.h>
43#include <string.h>
44#include <time.h>
45#include <ctype.h>
46
47
48#define ERROR_REPORT(num, val, msg)   fprintf(stderr, "error(%d):\t\"%s\"\t%s\n", (num), (val), (msg));
49#define CLEANUP(ptr)    do { if(NULL != ptr) { free(ptr); ptr = NULL; } } while(0)
50
51
52typedef struct __struct_Options
53/*
54**  Options to control how we perform.
55**
56**  mProgramName    Used in help text.
57**  mInput          File to read for input.
58**                  Default is stdin.
59**  mInputName      Name of the file.
60**  mOutput         Output file, append.
61**                  Default is stdout.
62**  mOutputName     Name of the file.
63**  mHelp           Whether or not help should be shown.
64*/
65{
66    const char* mProgramName;
67    FILE* mInput;
68    char* mInputName;
69    FILE* mOutput;
70    char* mOutputName;
71    int mHelp;
72}
73Options;
74
75
76typedef struct __struct_Switch
77/*
78**  Command line options.
79*/
80{
81    const char* mLongName;
82    const char* mShortName;
83    int mHasValue;
84    const char* mValue;
85    const char* mDescription;
86}
87Switch;
88
89#define DESC_NEWLINE "\n\t\t"
90
91static Switch gInputSwitch = {"--input", "-i", 1, NULL, "Specify input file." DESC_NEWLINE "stdin is default."};
92static Switch gOutputSwitch = {"--output", "-o", 1, NULL, "Specify output file." DESC_NEWLINE "Appends if file exists." DESC_NEWLINE "stdout is default."};
93static Switch gHelpSwitch = {"--help", "-h", 0, NULL, "Information on usage."};
94
95static Switch* gSwitches[] = {
96        &gInputSwitch,
97        &gOutputSwitch,
98        &gHelpSwitch
99};
100
101
102char* scanWhite(char* inScan)
103/*
104**  Scan for whitespace.
105*/
106{
107    char* retval = inScan;
108
109    while('\0' != *retval && 0 == isspace(*retval))
110    {
111        retval++;
112    }
113
114    return retval;
115}
116
117
118void trimWhite(char* inString)
119/*
120**  Remove any whitespace from the end of the string.
121*/
122{
123    int len = strlen(inString);
124
125    while(len)
126    {
127        len--;
128
129        if(isspace(*(inString + len)))
130        {
131            *(inString + len) = '\0';
132        }
133        else
134        {
135            break;
136        }
137    }
138}
139
140
141int nm2tsv(Options* inOptions)
142/*
143**  Read all input.
144**  Output tab separated value data.
145**
146**  We expect our data to be in a particular format.
147**  nm --format=bsd --size-sort --print-file-name --demangle
148*/
149{
150    int retval = 0;
151    char lineBuffer[4096];  /* yes, the are some very large symbols */
152    char* module = NULL;
153    char* size = NULL;
154    char* type = NULL;
155    char* symbol = NULL;
156
157    /*
158    **  Read in the nm file.
159    */
160    while(0 == retval && NULL != fgets(lineBuffer, sizeof(lineBuffer), inOptions->mInput))
161    {
162        trimWhite(lineBuffer);
163
164        /*
165        ** Find the various pieces of information we'll be looking for.
166        */
167        size = strchr(lineBuffer, ':');
168        if(NULL != size)
169        {
170            *size = '\0';
171            size++;
172
173            module = strrchr(lineBuffer, '/');
174            if(NULL == module)
175            {
176                module = lineBuffer;
177            }
178            else
179            {
180                *module = '\0';
181                module++;
182            }
183
184            type = scanWhite(size);
185            *type = '\0';
186            type++;
187
188            symbol = type + 1;
189            *symbol = '\0';
190            symbol++;
191
192            /*
193            **  Skip certain types.
194            */
195            switch(*type)
196            {
197                case '-':
198                    continue;
199                    break;
200                default:
201                    break;
202            }
203
204            /*
205            **  Simply output the data with a little more interpretation.
206            **  First is size.
207            */
208            fprintf(inOptions->mOutput, "%s\t", size);
209
210            /*
211            **  Type, CODE or DATA
212            */
213            switch(toupper(*type))
214            {
215                case 'T': /* text (code) */
216                case 'W': /* weak symbol ??? */
217                    fprintf(inOptions->mOutput, "CODE\t");
218                    break;
219                default:
220                    fprintf(inOptions->mOutput, "DATA\t");
221                    break;
222            }
223
224            /*
225            **  Scope, PUBLIC, STATIC, or UNDEF
226            */
227            if(islower(*type))
228            {
229                fprintf(inOptions->mOutput, "STATIC\t");
230            }
231            else
232            {
233                switch(*type)
234                {
235                    case '?':
236                        fprintf(inOptions->mOutput, "UNDEF\t");
237                        break;
238                    default:
239                        fprintf(inOptions->mOutput, "PUBLIC\t");
240                        break;
241                }
242            }
243
244            /*
245            **  Module name, segment.
246            */
247            fprintf(inOptions->mOutput, "%s\t", module);
248            fprintf(inOptions->mOutput, "%c\t", toupper(*type));
249
250            /*
251            **  Origin
252            */
253            fprintf(inOptions->mOutput, "UNDEF:%s:%c\t", module, toupper(*type));
254
255            /*
256            **  Symbol is last.
257            */
258            fprintf(inOptions->mOutput, "%s\n", symbol);
259        }
260        else
261        {
262            retval = __LINE__;
263            ERROR_REPORT(retval, lineBuffer, "Malformed input line.");
264        }
265    }
266
267    if(0 == retval && 0 != ferror(inOptions->mInput))
268    {
269        retval = __LINE__;
270        ERROR_REPORT(retval, inOptions->mInputName, "Unable to read file.");
271    }
272
273    return retval;
274}
275
276
277int initOptions(Options* outOptions, int inArgc, char** inArgv)
278/*
279**  returns int     0 if successful.
280*/
281{
282    int retval = 0;
283    int loop = 0;
284    int switchLoop = 0;
285    int match = 0;
286    const int switchCount = sizeof(gSwitches) / sizeof(gSwitches[0]);
287    Switch* current = NULL;
288
289    /*
290    **  Set any defaults.
291    */
292    memset(outOptions, 0, sizeof(Options));
293    outOptions->mProgramName = inArgv[0];
294    outOptions->mInput = stdin;
295    outOptions->mInputName = strdup("stdin");
296    outOptions->mOutput = stdout;
297    outOptions->mOutputName = strdup("stdout");
298
299    if(NULL == outOptions->mOutputName || NULL == outOptions->mInputName)
300    {
301        retval = __LINE__;
302        ERROR_REPORT(retval, "stdin/stdout", "Unable to strdup.");
303    }
304
305    /*
306    **  Go through and attempt to do the right thing.
307    */
308    for(loop = 1; loop < inArgc && 0 == retval; loop++)
309    {
310        match = 0;
311        current = NULL;
312
313        for(switchLoop = 0; switchLoop < switchCount && 0 == retval; switchLoop++)
314        {
315            if(0 == strcmp(gSwitches[switchLoop]->mLongName, inArgv[loop]))
316            {
317                match = __LINE__;
318            }
319            else if(0 == strcmp(gSwitches[switchLoop]->mShortName, inArgv[loop]))
320            {
321                match = __LINE__;
322            }
323
324            if(match)
325            {
326                if(gSwitches[switchLoop]->mHasValue)
327                {
328                    /*
329                    **  Attempt to absorb next option to fullfill value.
330                    */
331                    if(loop + 1 < inArgc)
332                    {
333                        loop++;
334
335                        current = gSwitches[switchLoop];
336                        current->mValue = inArgv[loop];
337                    }
338                }
339                else
340                {
341                    current = gSwitches[switchLoop];
342                }
343
344                break;
345            }
346        }
347
348        if(0 == match)
349        {
350            outOptions->mHelp = __LINE__;
351            retval = __LINE__;
352            ERROR_REPORT(retval, inArgv[loop], "Unknown command line switch.");
353        }
354        else if(NULL == current)
355        {
356            outOptions->mHelp = __LINE__;
357            retval = __LINE__;
358            ERROR_REPORT(retval, inArgv[loop], "Command line switch requires a value.");
359        }
360        else
361        {
362            /*
363            ** Do something based on address/swtich.
364            */
365            if(current == &gInputSwitch)
366            {
367                CLEANUP(outOptions->mInputName);
368                if(NULL != outOptions->mInput && stdin != outOptions->mInput)
369                {
370                    fclose(outOptions->mInput);
371                    outOptions->mInput = NULL;
372                }
373
374                outOptions->mInput = fopen(current->mValue, "r");
375                if(NULL == outOptions->mInput)
376                {
377                    retval = __LINE__;
378                    ERROR_REPORT(retval, current->mValue, "Unable to open input file.");
379                }
380                else
381                {
382                    outOptions->mInputName = strdup(current->mValue);
383                    if(NULL == outOptions->mInputName)
384                    {
385                        retval = __LINE__;
386                        ERROR_REPORT(retval, current->mValue, "Unable to strdup.");
387                    }
388                }
389            }
390            else if(current == &gOutputSwitch)
391            {
392                CLEANUP(outOptions->mOutputName);
393                if(NULL != outOptions->mOutput && stdout != outOptions->mOutput)
394                {
395                    fclose(outOptions->mOutput);
396                    outOptions->mOutput = NULL;
397                }
398
399                outOptions->mOutput = fopen(current->mValue, "a");
400                if(NULL == outOptions->mOutput)
401                {
402                    retval = __LINE__;
403                    ERROR_REPORT(retval, current->mValue, "Unable to open output file.");
404                }
405                else
406                {
407                    outOptions->mOutputName = strdup(current->mValue);
408                    if(NULL == outOptions->mOutputName)
409                    {
410                        retval = __LINE__;
411                        ERROR_REPORT(retval, current->mValue, "Unable to strdup.");
412                    }
413                }
414            }
415            else if(current == &gHelpSwitch)
416            {
417                outOptions->mHelp = __LINE__;
418            }
419            else
420            {
421                retval = __LINE__;
422                ERROR_REPORT(retval, current->mLongName, "No handler for command line switch.");
423            }
424        }
425    }
426
427    return retval;
428}
429
430
431void cleanOptions(Options* inOptions)
432/*
433**  Clean up any open handles.
434*/
435{
436    CLEANUP(inOptions->mInputName);
437    if(NULL != inOptions->mInput && stdin != inOptions->mInput)
438    {
439        fclose(inOptions->mInput);
440    }
441    CLEANUP(inOptions->mOutputName);
442    if(NULL != inOptions->mOutput && stdout != inOptions->mOutput)
443    {
444        fclose(inOptions->mOutput);
445    }
446
447    memset(inOptions, 0, sizeof(Options));
448}
449
450
451void showHelp(Options* inOptions)
452/*
453**  Show some simple help text on usage.
454*/
455{
456    int loop = 0;
457    const int switchCount = sizeof(gSwitches) / sizeof(gSwitches[0]);
458    const char* valueText = NULL;
459
460    printf("usage:\t%s [arguments]\n", inOptions->mProgramName);
461    printf("\n");
462    printf("arguments:\n");
463
464    for(loop = 0; loop < switchCount; loop++)
465    {
466        if(gSwitches[loop]->mHasValue)
467        {
468            valueText = " <value>";
469        }
470        else
471        {
472            valueText = "";
473        }
474
475        printf("\t%s%s\n", gSwitches[loop]->mLongName, valueText);
476        printf("\t %s%s", gSwitches[loop]->mShortName, valueText);
477        printf(DESC_NEWLINE "%s\n\n", gSwitches[loop]->mDescription);
478    }
479
480    printf("This tool normalizes nm output for use by other tools.\n");
481    printf("GNU nm is assumed for symbol type determination.\n");
482    printf("i.e. Use this tool to parse the output of:\n");
483    printf("\t/usr/bin/nm --format=bsd --size-sort --print-file-name --demangle <exefile>\n");
484}
485
486
487int main(int inArgc, char** inArgv)
488{
489    int retval = 0;
490    Options options;
491
492    retval = initOptions(&options, inArgc, inArgv);
493    if(options.mHelp)
494    {
495        showHelp(&options);
496    }
497    else if(0 == retval)
498    {
499        retval = nm2tsv(&options);
500    }
501
502    cleanOptions(&options);
503    return retval;
504}
505
506