首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >The Structure of import Library File (.lib)

The Structure of import Library File (.lib)

作者头像
战神伽罗
发布2022-01-09 09:50:36
5910
发布2022-01-09 09:50:36
举报

This article show the structure of import library file (.lib) used along with header to link against DLL

Introduction

Have you ever wondered about the contents of Microsoft's import library file? This article gives a brief description of .lib file structure and provides source to create import library given the name of export module (.dll; .sys; .exe files) and list of its exported functions (functions can be cherry-picked, so it's not necessary to provide full list).

Two Words About Calling Conventions and Name Decoration

On x86, we can use the following calling conventions for C function:

  1. __cdecl
  2. __stdcall
  3. __fastcall
  4. __vectorcall

On x64, we can use the following calling conventions for C function (__cdecl, __stdcall, __fastcall are ignored by compiler):

  1. default (function names are not decorated)
  2. __vectorcall

See this link for details about name decoration.

Import Library Structure

Import library is an archive, it starts with arch signature, it is an 8-byte string, namely:

!<arch>\n

After the signature, we have a bunch of files:

Image 1
Image 1

Each file starts with fixed-length header, following the arbitrary-length body (header contains length of the body):

Now let's consider symbols "hosted" by import library. Assuming that export module in question is BOOTVID.DLL, we will have three "predefined" symbols:

  1. __IMPORT_DESCRIPTOR_BOOTVID
  2. __NULL_IMPORT_DESCRIPTOR
  3. <7Fh> BOOTVID_NULL_THUNK_DATA // string starts with 0x7F

For each imported function, we will have two additional symbols. Let's consider this C function:

C++

void __stdcall VidDisplayString(char *pString);

In x86 version of import library, we will have:

  1. _VidDisplayString@4 // decorated function name
  2. __imp__VidDisplayString@4 // the same string prefixed by __imp_

In x64 version of import library, we will have:

  1. VidDisplayString
  2. __imp_VidDisplayString // the same string prefixed by __imp_

Now let's describe each file contents. First two files in archive contain all symbol names along with offsets to other files. Then we have three files dedicated to three "predefined" symbols. And finally, we have files dedicated to functions (one file for each function).

File 1 contains number of symbols field, symbol names and their corresponding offsets (offsets to another files from the beginning of archive). So File 3 is dedicated to __IMPORT_DESCRIPTOR_BOOTVID, File 4 is dedicated to __NULL_IMPORT_DESCRIPTOR, and so on. Note that only one file is dedicated to each function, despite the fact that function introduces two symbols.

File 2 contains offset table, symbol names and their corresponding indexes (indexes into offset table). It is essentially the same information given in a different form. Note that indexing starts from 1.

Files 3, 4, 5 are dedicated to __IMPORT_DESCRIPTOR_BOOTVID, __NULL_IMPORT_DESCRIPTOR, <7F> BOOTVID_NULL_THUNK_DATA. I won't picture them in detail, they are essentially the same for all import libraries. Instead, I want to focus on File 6 and picture it in detail. Let's see the structure that represents file header:

C++

struct FILE_HEADER                // size = 0x3C
{
    char Part1[16];
    char Id[24];
    char Part3[8];
    char BodyLength[10];
    char Part5[2];
};

Id field holds random number generated by time function (in ASCII form), BodyLength field holds the length of the following file body (in ASCII form). After file header, we have symbol descriptor:

C++

struct SYMBOL_DESCRIPTOR          // size = 0x14
{
    WORD a;
    WORD b;
    WORD c;
    WORD Architecture;
    DWORD Id;
    DWORD Length;
    union
    {
        WORD Hint;
        WORD Ordinal;
        WORD Value;
    }
    WORD Type;
};

Architecture field contains 0x14C for x86 and 0x8664 for x64. Id field is the same random number represented in binary form. Hint / Ordinal field contains function hint / ordinal, Type field specifies import type (you will find description further). Length field contains the summary length of two following strings (including their null-characters):

Import / Export Scenarios

We will consider three possible scenarios:

  1. We compile export module and export function without module definition file. Input: Source: C++ __declspec(dllexport) void __cdecl function1() {} __declspec(dllexport) void __stdcall function2() {} __declspec(dllexport) void __fastcall function3() {} __declspec(dllexport) void __vectorcall function4() {} Output for x86: Import library (SYMBOL_DESCRIPTOR): C++ Name = _function1, Hint = 0, Type = 8 Name = _function2@0, Hint = 1, Type = 4 Name = @function3@0, Hint = 2, Type = 4 Name = function4@@0, Hint = 3, Type = 4 Import library stores Hint (starts from 0, represents index into AddressOfNames array). Export module's export (AddressOfNames, AddressOfNameOrdinals): C++ Name = _function1, Ordinal = index into AddressOfFunctions array Name = _function2@0, Ordinal = index into AddressOfFunctions array Name = @function3@0, Ordinal = index into AddressOfFunctions array Name = function4@@0, Ordinal = index into AddressOfFunctions array Despite the name Ordinal, AddressOfNameOrdinals stores Index (starts from 0, represents index into AddressOfFunctions array). Import module's import (IMAGE_IMPORT_BY_NAME): C++ Name = _function1, Hint = 0 Name = _function2@0, Hint = 1 Name = @function3@0, Hint = 2 Name = function4@@0, Hint = 3 Output for x64: Import library (SYMBOL_DESCRIPTOR): C++ Name = function1, Hint = 0, Type = 4 Name = function2, Hint = 1, Type = 4 Name = function3, Hint = 2, Type = 4 Name = function4@@0, Hint = 3, Type = 4 Import library stores Hint (starts from 0, represents index into AddressOfNames array). Export module's export (AddressOfNames, AddressOfNameOrdinals): C++ Name = function1, Ordinal = index into AddressOfFunctions array Name = function2, Ordinal = index into AddressOfFunctions array Name = function3, Ordinal = index into AddressOfFunctions array Name = function4@@0, Ordinal = index into AddressOfFunctions array Despite the name Ordinal, AddressOfNameOrdinals stores Index (starts from 0, represents index into AddressOfFunctions array). Import module's import (IMAGE_IMPORT_BY_NAME): C++ Name = function1, Hint = 0 Name = function2, Hint = 1 Name = function3, Hint = 2 Name = function4@@0, Hint = 3
  2. We compile some DLL and export function using module definition file (we specify function name). Input: Source: C++ void __cdecl function1() {} void __stdcall function2() {} void __fastcall function3() {} void __vectorcall function4() {} Def file: C++ EXPORTS function1 function2 function3 function4 Output for x86: Import library (SYMBOL_DESCRIPTOR): C++ Name = _function1, Hint = 0, Type = 8 Name = _function2@0, Hint = 1, Type = 0xC Name = @function3@0, Hint = 2, Type = 0xC Name = function4@@0, Hint = 3, Type = 0xC Import library stores Hint (starts from 0, represents index into AddressOfNames array). Export module's export (AddressOfNames, AddressOfNameOrdinals): C++ // function names are not decorated Name = function1, Ordinal = index into AddressOfFunctions array Name = function2, Ordinal = index into AddressOfFunctions array Name = function3, Ordinal = index into AddressOfFunctions array Name = function4, Ordinal = index into AddressOfFunctions array Despite the name Ordinal, AddressOfNameOrdinals stores Index (starts from 0, represents index into AddressOfFunctions array). Import module's import (IMAGE_IMPORT_BY_NAME): C++ // function names are not decorated Name = function1, Hint = 0 Name = function2, Hint = 1 Name = function3, Hint = 2 Name = function4, Hint = 3 Output for x64: Import library (SYMBOL_DESCRIPTOR): C++ Name = function1, Hint = 0, Type = 4 Name = function2, Hint = 1, Type = 4 Name = function3, Hint = 2, Type = 4 Name = function4@@0, Hint = 3, Type = 0xC Import library stores Hint (starts from 0, represents index into AddressOfNames array). Export module's export (AddressOfNames, AddressOfNameOrdinals): C++ // function names are not decorated Name = function1, Ordinal = index into AddressOfFunctions array Name = function2, Ordinal = index into AddressOfFunctions array Name = function3, Ordinal = index into AddressOfFunctions array Name = function4, Ordinal = index into AddressOfFunctions array Despite the name Ordinal, AddressOfNameOrdinals stores Index (starts from 0, represents index into AddressOfFunctions array). Import module's import (IMAGE_IMPORT_BY_NAME): C++ // function names are not decorated Name = function1, Hint = 0 Name = function2, Hint = 1 Name = function3, Hint = 2 Name = function4, Hint = 3
  3. We compile some DLL and export function using module definition file (we specify function name and ordinal). Input: Source: C++ void __cdecl function1() {} void __stdcall function2() {} void __fastcall function3() {} void __vectorcall function4() {} Def file: C++ EXPORTS function1 @1 function2 @2 function3 @3 function4 @4 Output for x86: Import library (SYMBOL_DESCRIPTOR): C++ Name = _function1, Ordinal = 1, Type = 0 Name = _function2@0, Ordinal = 2, Type = 0 Name = @function3@0, Ordinal = 3, Type = 0 Name = function4@@0, Ordinal = 4, Type = 0 Import library stores Ordinal (starts from 1, represents alternative function name). Export module's export (AddressOfNames, AddressOfNameOrdinals): C++ // function names are not decorated Name = function1, Ordinal = index into AddressOfFunctions array Name = function2, Ordinal = index into AddressOfFunctions array Name = function3, Ordinal = index into AddressOfFunctions array Name = function4, Ordinal = index into AddressOfFunctions array Despite the name Ordinal, AddressOfNameOrdinals stores Index (starts from 0, represents index into AddressOfFunctions array). If we would use NONAME directive for some function, it would not have corresponding entries in AddressOfNames and AddressOfNameOrdinals arrays. Import module's import (now we have Ordinal instead of IMAGE_IMPORT_BY_NAME): C++ Ordinal = 1 Ordinal = 2 Ordinal = 3 Ordinal = 4 Output for x64: Import library (SYMBOL_DESCRIPTOR): C++ Name = function1, Ordinal = 1, Type = 0 Name = function2, Ordinal = 2, Type = 0 Name = function3, Ordinal = 3, Type = 0 Name = function4@@0, Ordinal = 4, Type = 0 Import library stores Ordinal (starts from 1, represents alternative function name). Export module's export (AddressOfNames, AddressOfNameOrdinals): C++ // function names are not decorated Name = function1, Ordinal = index into AddressOfFunctions array Name = function2, Ordinal = index into AddressOfFunctions array Name = function3, Ordinal = index into AddressOfFunctions array Name = function4, Ordinal = index into AddressOfFunctions array Despite the name Ordinal, AddressOfNameOrdinals stores Index (starts from 0, represents index into AddressOfFunctions array). If we would use NONAME directive for some function, it would not have corresponding entries in AddressOfNames and AddressOfNameOrdinals arrays. Import module's import (now we have Ordinal instead of IMAGE_IMPORT_BY_NAME): C++ Ordinal = 1 Ordinal = 2 Ordinal = 3 Ordinal = 4

Import by Name

When we import function by Name, we obtain its address in the following way:

C++

if (strcmp(FunctionName, AddressOfNames[Hint]))
{
  for (int i = 0; i < NumberOfNames; ++i)
  {
    if (!strcmp(FunctionName, AddressOfNames[i]))
    {
      Hint = i
      break
    }
  }
}
Index = AddressOfNameOrdinals[Hint]
Address = AddressOfFunctions[Index]

Import by Ordinal

When we import function by Ordinal, we obtain its address in the following way:

C++

Address = AddressOfFunctions[Ordinal - Base]

Two Words About Another def File Directives

INTERNALNAME allows you to use another name ("internal") in your export module's code. Consider this example:

Source:

C++

void __stdcall internal_name() {}

Def file:

C++

EXPORTS

external_name = internal_name

From import/export mechanism point of view, it is equivalent to:

Source:

C++

void __stdcall external_name() {}

Def file:

C++

EXPORTS

external_name

PRIVATE allows you to build import library without some symbol ("partial" import library).

Using dumpbin to Get Export Information

We can use dumpbin tool to get information about export module's exports (for which we do not have source). Possible dumpbin outputs:

Here we see decorated function name, hint and ordinal.

The same, except function name is not decorated.

Here we see ordinal only. Since module does not store function name, hint also does not exist.

Note that export will always have ordinal, however export will not necessarily have name and hint.

Since calling convention is not stored, we can determine it by examining function's code by disassembler. On x86, it is usually __stdcall or __fastcall, on x64, it is usually default calling convention.

Using the Code

For each imported function, we need to specify:

  1. Name
  2. Value
  3. Size of argument list in bytes
  4. Calling convention
  5. Import type

Import types:

  1. IMPORT_BY_SPECIFIED_NAME
  2. IMPORT_BY_DECORATED_NAME
  3. IMPORT_BY_ORDINAL

IMPORT_BY_SPECIFIED_NAME:

Import module will hold undecorated function name and Hint that is given by Value parameter.

Export module must store undecorated function name.

IMPORT_BY_DECORATED_NAME:

Import module will hold decorated function name and Hint that is given by Value parameter.

Export module must store decorated function name.

IMPORT_BY_ORDINAL:

Import module will hold Ordinal that is given by Value parameter.

Export module does not need to store function name.

Size of argument list in bytes parameter is needed to perform function name decoration.

The code to generate import library:

C++

int main(int argc, char* argv[])
{
    char *pName;
    SYMBOL_INFO *pSymbolList;

    pName = "BOOTVID";

    pSymbolList = CreateSymbolList(pName);

    g_InfoAll.bX64 = FALSE;    // specify TRUE for x64

    AddFunction(pSymbolList, "VidDisplayString", 4, 4, 
                CALLING_CONVENTION_STDCALL, IMPORT_BY_SPECIFIED_NAME);

    WriteImportLibrary(pName, ".dll", pSymbolList);

    DestroySymbolList(pSymbolList);

    return 0;
}

Here, we specify architecture (x86), export module name (BOOTVID), export module extension (.dll) and function list.

In our import module's source, we would declare the function in the following way:

C++

__declspec(dllimport) void __stdcall VidDisplayString(char *pString);      // C function

Afterword

We have not considered variables, DATA and CONSTANT directives, importing using .def file, purpose of __imp_, and C++ names. I will update the article later to cover all these topics. Thank you for reading.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022/阅读数 22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Introduction
  • Two Words About Calling Conventions and Name Decoration
  • Import Library Structure
  • Import / Export Scenarios
  • Import by Name
  • Import by Ordinal
  • Two Words About Another def File Directives
  • Using dumpbin to Get Export Information
  • Using the Code
  • Afterword
  • License
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档