Writing NSIS Plugins

From Open Watcom

Jump to: navigation, search

I am currently working on getting plugins compiled with Open Watcom to work with NSIS. The NSIS compiler is not loading the dlls I compile with Open Watcom. At the moment this page documents my research in this area. I am posting my progress to the newsgroup as well. Feel free to make notes on this page, the talk page or the newsgroup if you wish to be of assistance. --Zippy1981 23:47, 3 January 2007 (PST)

Contents

Overview of NSIS plugins

NSIS plugins are simply DLLs. The functions provided by these functions are exported functions that have a specific array of parameters. That signature is as follows.

__declspec(dllexport) void __cdecl TestFunc(HWND hwndParent, int string_size,
                                      char *variables, stack_t **stacktop)

Parameters are always passed to plugins as strings. int string_size, char *variables behave as int argc, char *argv[] do for main() in an executable.

How NSIS loads plugins

There is a file called Plugins.cpp in the NSIS source. This contains the class Plugins with the member Plugins::FindPlugins() that scans the plugins directory for dll files. FindPlugins() then calls Plugins::GetExports(), which scans the found dll files for NSIS plugin commands. These functions display their output via fprintf (g_output, ... ) calls. g_output is a global variable pointing to stdout in the command line compiler or the TextArea "console" on the gui compiler.

Getting a closer look

The Plugins class is very well written. As such it is trivial to wrote a program that uses this class and simply set g_output equal to 'stdout to list all the plugins functions that get loaded. I have developed such a program call DllScanner.

When I started this effort, it became apparent that that Plugins.cpp would not compile in OW without significant modification. Thanks to Peter Chapin for making that explicitly clear. Therefore I used Microsoft Visual C++ 2005 to compile the program.

Dllscanner.cpp contains the code I wrote. Its contents are below. I linked in Plugins.cpp and kept adding source files from NSIS until I stopped getting unresolved symbol errors from the linker. The contents of this file are below. After I got that working I make [[#Plugins::GetExports()]|GetExports()] be more verbose about what it was doing.

This verbosity has revealed that the Plugins class does not find an export header in the OpenWatcom DLL. The output for a Visual Studio compiled dll is as follows:

Scanning c:\Program Files\NSIS\Plugins\nsODBC.dll:
- 4 sections found.
- Exports found at section 1.
 - nsODBC::AddDSN
 - nsODBC::AddSysDSN
 - nsODBC::ConfDSN
 - nsODBC::ConfSysDSN
 - nsODBC::RemoveDSN
 - nsODBC::RemoveDefDSN
 - nsODBC::RemoveSysDSN

For an OpenWatcom dll the results are as follows:

Output for a watcom compiled dll is as follows.
Scanning c:\Program Files\NSIS\Plugins\noname.dll:
- 5 sections found.

The message Exports found at section n. is displayed upon success of this if statement:

if
  (va <= ExportDirVA && 
   va + FIX_ENDIAN_INT32(sections[i].Misc.VirtualSize) 
     >= 
   ExportDirVA + ExportDirSize)


The values of the variables in that statement are:

va               Virtual address of the current section
ExpirtDirVA      The virtual address of the Export Directory
sections[]       An array of IMAGE_SECTION_HEADER(s) This is gotten by       
                 calling IMAGE_FIRST_SECTION() on the PIMAGE_NT_HEADERS
ExportDirSize    The size of the export data dirctory.

The problem is obviously that the Headers of the DLL files are different from the ones generated by Visual Studio. I just don't know why.

DllScanner Source code

DllScanner.cpp

Contents of the file:

#include <stdio.h>
#include <stdlib.h>
#include "Plugins.h"

using namespace std;


FILE *g_output = stdout;
int g_display_errors = true;

void main (int argc, char *argv[]) {
    printf ("DLL Scanner for NSIS.\n");
    if (argc != 2) {
        fprintf (stderr, "\tUsage:\n\t\tDllScanner PathToDlls\n");
        exit (1);
    }

    Plugins oPlugins;
    string *searchPath;
	searchPath = new string(argv[1]);
    oPlugins.FindCommands(*searchPath, true);
    return;
}

Plugins::GetExports()

void Plugins::GetExports(const string &pathToDll, bool displayInfo)
{
  fprintf (g_output, "Scanning %s:\n", pathToDll.c_str());
  vector<unsigned char> dlldata;
  PIMAGE_NT_HEADERS NTHeaders;
  try {
    dlldata = read_file(pathToDll);
    NTHeaders = CResourceEditor::GetNTHeaders(&dlldata[0]);
  } catch (std::runtime_error& err) {
    fprintf (g_output, "%s\n", err.what());
    return;
  }

  const string dllName = remove_file_extension(get_file_name(pathToDll));

  FIX_ENDIAN_INT16_INPLACE(NTHeaders->FileHeader.Characteristics);
  if (NTHeaders->FileHeader.Characteristics & IMAGE_FILE_DLL)
  {
    FIX_ENDIAN_INT32_INPLACE(NTHeaders->OptionalHeader.NumberOfRvaAndSizes);
	if (NTHeaders->OptionalHeader.NumberOfRvaAndSizes <= IMAGE_DIRECTORY_ENTRY_EXPORT) {
      fprintf
		(g_output, "The statement %s evaluated to non zero.\n\tI'll be damned if I know what that means",
		"(NTHeaders->OptionalHeader.NumberOfRvaAndSizes <= IMAGE_DIRECTORY_ENTRY_EXPORT)");
      return;
	}

    DWORD ExportDirVA = NTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
    DWORD ExportDirSize = NTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;
    PIMAGE_SECTION_HEADER sections = IMAGE_FIRST_SECTION(NTHeaders);

    FIX_ENDIAN_INT32_INPLACE(ExportDirVA);
    FIX_ENDIAN_INT32_INPLACE(ExportDirSize);

    WORD num_sections = FIX_ENDIAN_INT16(NTHeaders->FileHeader.NumberOfSections);

    fprintf(g_output, "- %d sections found.\n", num_sections);
	for (DWORD i = 0; i < num_sections; i++)
    {
      DWORD va = FIX_ENDIAN_INT32(sections[i].VirtualAddress);
      if (va <= ExportDirVA
          && va + FIX_ENDIAN_INT32(sections[i].Misc.VirtualSize) >= ExportDirVA + ExportDirSize)
      {
		fprintf(g_output, "- Exports found at section %d.\n", i);
        DWORD prd = FIX_ENDIAN_INT32(sections[i].PointerToRawData);
        PIMAGE_EXPORT_DIRECTORY exports = PIMAGE_EXPORT_DIRECTORY(&dlldata[0] + prd + ExportDirVA - va);
        DWORD na = FIX_ENDIAN_INT32(exports->AddressOfNames);
        unsigned long *names = (unsigned long*)((unsigned long) exports + (char *) na - ExportDirVA);
        for (unsigned long j = 0; j < FIX_ENDIAN_INT32(exports->NumberOfNames); j++)
        {
          const string name = string((char*)exports + FIX_ENDIAN_INT32(names[j]) - ExportDirVA);
          const string signature = dllName + "::" + name;
          const string lcsig = lowercase(signature);
          m_command_to_path[lcsig] = pathToDll;
          m_command_lowercase_to_command[lcsig] = signature;
          if (displayInfo)
            fprintf(g_output, " - %s\n", signature.c_str());
		}
        break;
      }
	} 
  } else {
    fprintf(g_output, "Does not appear to be a valid DLL\n");
  }
}

References

You need the following to build NSIS yourself.

Personal tools