| /* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
| * |
| * ***** BEGIN LICENSE BLOCK ***** |
| * Version: MPL 1.1/GPL 2.0/LGPL 2.1 |
| * |
| * The contents of this file are subject to the Mozilla Public License Version |
| * 1.1 (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * http://www.mozilla.org/MPL/ |
| * |
| * Software distributed under the License is distributed on an "AS IS" basis, |
| * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License |
| * for the specific language governing rights and limitations under the |
| * License. |
| * |
| * The Original Code is nm2tsv.c code, released |
| * Oct 10, 2002. |
| * |
| * The Initial Developer of the Original Code is |
| * Netscape Communications Corporation. |
| * Portions created by the Initial Developer are Copyright (C) 2002 |
| * the Initial Developer. All Rights Reserved. |
| * |
| * Contributor(s): |
| * Garrett Arch Blythe, 10-October-2002 |
| * |
| * Alternatively, the contents of this file may be used under the terms of |
| * either the GNU General Public License Version 2 or later (the "GPL"), or |
| * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), |
| * in which case the provisions of the GPL or the LGPL are applicable instead |
| * of those above. If you wish to allow use of your version of this file only |
| * under the terms of either the GPL or the LGPL, and not to allow others to |
| * use your version of this file under the terms of the MPL, indicate your |
| * decision by deleting the provisions above and replace them with the notice |
| * and other provisions required by the GPL or the LGPL. If you do not delete |
| * the provisions above, a recipient may use your version of this file under |
| * the terms of any one of the MPL, the GPL or the LGPL. |
| * |
| * ***** END LICENSE BLOCK ***** */ |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <time.h> |
| #include <ctype.h> |
| |
| |
| #define ERROR_REPORT(num, val, msg) fprintf(stderr, "error(%d):\t\"%s\"\t%s\n", (num), (val), (msg)); |
| #define CLEANUP(ptr) do { if(NULL != ptr) { free(ptr); ptr = NULL; } } while(0) |
| |
| |
| typedef struct __struct_Options |
| /* |
| ** Options to control how we perform. |
| ** |
| ** mProgramName Used in help text. |
| ** mInput File to read for input. |
| ** Default is stdin. |
| ** mInputName Name of the file. |
| ** mOutput Output file, append. |
| ** Default is stdout. |
| ** mOutputName Name of the file. |
| ** mHelp Whether or not help should be shown. |
| */ |
| { |
| const char* mProgramName; |
| FILE* mInput; |
| char* mInputName; |
| FILE* mOutput; |
| char* mOutputName; |
| int mHelp; |
| } |
| Options; |
| |
| |
| typedef struct __struct_Switch |
| /* |
| ** Command line options. |
| */ |
| { |
| const char* mLongName; |
| const char* mShortName; |
| int mHasValue; |
| const char* mValue; |
| const char* mDescription; |
| } |
| Switch; |
| |
| #define DESC_NEWLINE "\n\t\t" |
| |
| static Switch gInputSwitch = {"--input", "-i", 1, NULL, "Specify input file." DESC_NEWLINE "stdin is default."}; |
| static Switch gOutputSwitch = {"--output", "-o", 1, NULL, "Specify output file." DESC_NEWLINE "Appends if file exists." DESC_NEWLINE "stdout is default."}; |
| static Switch gHelpSwitch = {"--help", "-h", 0, NULL, "Information on usage."}; |
| |
| static Switch* gSwitches[] = { |
| &gInputSwitch, |
| &gOutputSwitch, |
| &gHelpSwitch |
| }; |
| |
| |
| char* scanWhite(char* inScan) |
| /* |
| ** Scan for whitespace. |
| */ |
| { |
| char* retval = inScan; |
| |
| while('\0' != *retval && 0 == isspace(*retval)) |
| { |
| retval++; |
| } |
| |
| return retval; |
| } |
| |
| |
| void trimWhite(char* inString) |
| /* |
| ** Remove any whitespace from the end of the string. |
| */ |
| { |
| int len = strlen(inString); |
| |
| while(len) |
| { |
| len--; |
| |
| if(isspace(*(inString + len))) |
| { |
| *(inString + len) = '\0'; |
| } |
| else |
| { |
| break; |
| } |
| } |
| } |
| |
| |
| int nm2tsv(Options* inOptions) |
| /* |
| ** Read all input. |
| ** Output tab separated value data. |
| ** |
| ** We expect our data to be in a particular format. |
| ** nm --format=bsd --size-sort --print-file-name --demangle |
| */ |
| { |
| int retval = 0; |
| char lineBuffer[4096]; /* yes, the are some very large symbols */ |
| char* module = NULL; |
| char* size = NULL; |
| char* type = NULL; |
| char* symbol = NULL; |
| |
| /* |
| ** Read in the nm file. |
| */ |
| while(0 == retval && NULL != fgets(lineBuffer, sizeof(lineBuffer), inOptions->mInput)) |
| { |
| trimWhite(lineBuffer); |
| |
| /* |
| ** Find the various pieces of information we'll be looking for. |
| */ |
| size = strchr(lineBuffer, ':'); |
| if(NULL != size) |
| { |
| *size = '\0'; |
| size++; |
| |
| module = strrchr(lineBuffer, '/'); |
| if(NULL == module) |
| { |
| module = lineBuffer; |
| } |
| else |
| { |
| *module = '\0'; |
| module++; |
| } |
| |
| type = scanWhite(size); |
| *type = '\0'; |
| type++; |
| |
| symbol = type + 1; |
| *symbol = '\0'; |
| symbol++; |
| |
| /* |
| ** Skip certain types. |
| */ |
| switch(*type) |
| { |
| case '-': |
| continue; |
| break; |
| default: |
| break; |
| } |
| |
| /* |
| ** Simply output the data with a little more interpretation. |
| ** First is size. |
| */ |
| fprintf(inOptions->mOutput, "%s\t", size); |
| |
| /* |
| ** Type, CODE or DATA |
| */ |
| switch(toupper(*type)) |
| { |
| case 'T': /* text (code) */ |
| case 'W': /* weak symbol ??? */ |
| fprintf(inOptions->mOutput, "CODE\t"); |
| break; |
| default: |
| fprintf(inOptions->mOutput, "DATA\t"); |
| break; |
| } |
| |
| /* |
| ** Scope, PUBLIC, STATIC, or UNDEF |
| */ |
| if(islower(*type)) |
| { |
| fprintf(inOptions->mOutput, "STATIC\t"); |
| } |
| else |
| { |
| switch(*type) |
| { |
| case '?': |
| fprintf(inOptions->mOutput, "UNDEF\t"); |
| break; |
| default: |
| fprintf(inOptions->mOutput, "PUBLIC\t"); |
| break; |
| } |
| } |
| |
| /* |
| ** Module name, segment. |
| */ |
| fprintf(inOptions->mOutput, "%s\t", module); |
| fprintf(inOptions->mOutput, "%c\t", toupper(*type)); |
| |
| /* |
| ** Origin |
| */ |
| fprintf(inOptions->mOutput, "UNDEF:%s:%c\t", module, toupper(*type)); |
| |
| /* |
| ** Symbol is last. |
| */ |
| fprintf(inOptions->mOutput, "%s\n", symbol); |
| } |
| else |
| { |
| retval = __LINE__; |
| ERROR_REPORT(retval, lineBuffer, "Malformed input line."); |
| } |
| } |
| |
| if(0 == retval && 0 != ferror(inOptions->mInput)) |
| { |
| retval = __LINE__; |
| ERROR_REPORT(retval, inOptions->mInputName, "Unable to read file."); |
| } |
| |
| return retval; |
| } |
| |
| |
| int initOptions(Options* outOptions, int inArgc, char** inArgv) |
| /* |
| ** returns int 0 if successful. |
| */ |
| { |
| int retval = 0; |
| int loop = 0; |
| int switchLoop = 0; |
| int match = 0; |
| const int switchCount = sizeof(gSwitches) / sizeof(gSwitches[0]); |
| Switch* current = NULL; |
| |
| /* |
| ** Set any defaults. |
| */ |
| memset(outOptions, 0, sizeof(Options)); |
| outOptions->mProgramName = inArgv[0]; |
| outOptions->mInput = stdin; |
| outOptions->mInputName = strdup("stdin"); |
| outOptions->mOutput = stdout; |
| outOptions->mOutputName = strdup("stdout"); |
| |
| if(NULL == outOptions->mOutputName || NULL == outOptions->mInputName) |
| { |
| retval = __LINE__; |
| ERROR_REPORT(retval, "stdin/stdout", "Unable to strdup."); |
| } |
| |
| /* |
| ** Go through and attempt to do the right thing. |
| */ |
| for(loop = 1; loop < inArgc && 0 == retval; loop++) |
| { |
| match = 0; |
| current = NULL; |
| |
| for(switchLoop = 0; switchLoop < switchCount && 0 == retval; switchLoop++) |
| { |
| if(0 == strcmp(gSwitches[switchLoop]->mLongName, inArgv[loop])) |
| { |
| match = __LINE__; |
| } |
| else if(0 == strcmp(gSwitches[switchLoop]->mShortName, inArgv[loop])) |
| { |
| match = __LINE__; |
| } |
| |
| if(match) |
| { |
| if(gSwitches[switchLoop]->mHasValue) |
| { |
| /* |
| ** Attempt to absorb next option to fullfill value. |
| */ |
| if(loop + 1 < inArgc) |
| { |
| loop++; |
| |
| current = gSwitches[switchLoop]; |
| current->mValue = inArgv[loop]; |
| } |
| } |
| else |
| { |
| current = gSwitches[switchLoop]; |
| } |
| |
| break; |
| } |
| } |
| |
| if(0 == match) |
| { |
| outOptions->mHelp = __LINE__; |
| retval = __LINE__; |
| ERROR_REPORT(retval, inArgv[loop], "Unknown command line switch."); |
| } |
| else if(NULL == current) |
| { |
| outOptions->mHelp = __LINE__; |
| retval = __LINE__; |
| ERROR_REPORT(retval, inArgv[loop], "Command line switch requires a value."); |
| } |
| else |
| { |
| /* |
| ** Do something based on address/swtich. |
| */ |
| if(current == &gInputSwitch) |
| { |
| CLEANUP(outOptions->mInputName); |
| if(NULL != outOptions->mInput && stdin != outOptions->mInput) |
| { |
| fclose(outOptions->mInput); |
| outOptions->mInput = NULL; |
| } |
| |
| outOptions->mInput = fopen(current->mValue, "r"); |
| if(NULL == outOptions->mInput) |
| { |
| retval = __LINE__; |
| ERROR_REPORT(retval, current->mValue, "Unable to open input file."); |
| } |
| else |
| { |
| outOptions->mInputName = strdup(current->mValue); |
| if(NULL == outOptions->mInputName) |
| { |
| retval = __LINE__; |
| ERROR_REPORT(retval, current->mValue, "Unable to strdup."); |
| } |
| } |
| } |
| else if(current == &gOutputSwitch) |
| { |
| CLEANUP(outOptions->mOutputName); |
| if(NULL != outOptions->mOutput && stdout != outOptions->mOutput) |
| { |
| fclose(outOptions->mOutput); |
| outOptions->mOutput = NULL; |
| } |
| |
| outOptions->mOutput = fopen(current->mValue, "a"); |
| if(NULL == outOptions->mOutput) |
| { |
| retval = __LINE__; |
| ERROR_REPORT(retval, current->mValue, "Unable to open output file."); |
| } |
| else |
| { |
| outOptions->mOutputName = strdup(current->mValue); |
| if(NULL == outOptions->mOutputName) |
| { |
| retval = __LINE__; |
| ERROR_REPORT(retval, current->mValue, "Unable to strdup."); |
| } |
| } |
| } |
| else if(current == &gHelpSwitch) |
| { |
| outOptions->mHelp = __LINE__; |
| } |
| else |
| { |
| retval = __LINE__; |
| ERROR_REPORT(retval, current->mLongName, "No handler for command line switch."); |
| } |
| } |
| } |
| |
| return retval; |
| } |
| |
| |
| void cleanOptions(Options* inOptions) |
| /* |
| ** Clean up any open handles. |
| */ |
| { |
| CLEANUP(inOptions->mInputName); |
| if(NULL != inOptions->mInput && stdin != inOptions->mInput) |
| { |
| fclose(inOptions->mInput); |
| } |
| CLEANUP(inOptions->mOutputName); |
| if(NULL != inOptions->mOutput && stdout != inOptions->mOutput) |
| { |
| fclose(inOptions->mOutput); |
| } |
| |
| memset(inOptions, 0, sizeof(Options)); |
| } |
| |
| |
| void showHelp(Options* inOptions) |
| /* |
| ** Show some simple help text on usage. |
| */ |
| { |
| int loop = 0; |
| const int switchCount = sizeof(gSwitches) / sizeof(gSwitches[0]); |
| const char* valueText = NULL; |
| |
| printf("usage:\t%s [arguments]\n", inOptions->mProgramName); |
| printf("\n"); |
| printf("arguments:\n"); |
| |
| for(loop = 0; loop < switchCount; loop++) |
| { |
| if(gSwitches[loop]->mHasValue) |
| { |
| valueText = " <value>"; |
| } |
| else |
| { |
| valueText = ""; |
| } |
| |
| printf("\t%s%s\n", gSwitches[loop]->mLongName, valueText); |
| printf("\t %s%s", gSwitches[loop]->mShortName, valueText); |
| printf(DESC_NEWLINE "%s\n\n", gSwitches[loop]->mDescription); |
| } |
| |
| printf("This tool normalizes nm output for use by other tools.\n"); |
| printf("GNU nm is assumed for symbol type determination.\n"); |
| printf("i.e. Use this tool to parse the output of:\n"); |
| printf("\t/usr/bin/nm --format=bsd --size-sort --print-file-name --demangle <exefile>\n"); |
| } |
| |
| |
| int main(int inArgc, char** inArgv) |
| { |
| int retval = 0; |
| Options options; |
| |
| retval = initOptions(&options, inArgc, inArgv); |
| if(options.mHelp) |
| { |
| showHelp(&options); |
| } |
| else if(0 == retval) |
| { |
| retval = nm2tsv(&options); |
| } |
| |
| cleanOptions(&options); |
| return retval; |
| } |
| |