openssl и __declspec(dllexport)

"Когда вы говорите Иван Васильевич, такое ощущение , что вы бредите..."

Именно так можно описать ситуацию, когда надо понять почему не идет отладка кода библиотек ssleay32 проекта openssl (cdt дебаггер) из под среды Qt Creator.

Как связана отладка и ключи сборки библиотек и самое главное как связана экспортируемость функций.

Ситуация под Windows 10-64 , Qt Creator 2.4.1, Qt 4.8.1 , openssl 1.0.2.

Оказывается библиотека ssleay32.dll собирается (у нас) под Qt Creator 2.4.1) без использования директивы __declspec(dllexport) (экспорт функций).

Почему?... Да потому ,что в исходниках opensssl есть файл ssleay32.def для указания экспортных функций в явном виде при сборки.

DEF_FILE - эта переменная используется под Windows для явного указания экспортируемых функций в создаваемой библиотеке.
DEF расшифровывается как Export Definition File.

DEF_FILE = libeay32.def НА САМОМ ДЕЛЕ нужен если у вас в исходниках нет директив (префиксов) для определения экспорта функций , а именоо : __declspec( dllexport ).

Разработчики openssl решили использовать вариант def файла, вместо того , чтобы прописать префиксы для экспортируемых функций как __declspec( dllexport ).

Тогда использование DEF_FILE = libeay32.def делает функции libeay32.dll экспортируемыми.

Может показаться , что экспортируемость функций по-видимому как-то связана с возможностью входить отладчиком из Qt creator в эти функции (примечание: и это не правда).

фотка 1

Может показаться , что QtNetwork.lib собирается "нормально" с экспортируемыми функциями. Вот кусок файла map сборки:

  Address         Publics by Value              Rva+Base       Lib:Object

 0000:00000000       __except_list              00000000     <absolute>
 0000:00000540       ___safe_se_handler_count   00000540     <absolute>
 0000:00009876       __ldused                   00009876     <absolute>
 0000:00009876       __fltused                  00009876     <absolute>
 0000:00000000       ___ImageBase               64000000     <linker-defined>
 0001:0000e1c0       ??9QUrlInfo@@QBE_NABV0@@Z  6400f1c0 f i qftp.obj
 0001:0000e1f0       ?tr@QFtp@@SA?AVQString@@PBD0@Z 6400f1f0 f i qftp.obj
 0001:0000e230       ?trUtf8@QFtp@@SA?AVQString@@PBD0@Z 6400f230 f i qftp.obj
 0001:0000e270       ?tr@QFtp@@SA?AVQString@@PBD0H@Z 6400f270 f i qftp.obj
 0001:0000e2c0       ?trUtf8@QFtp@@SA?AVQString@@PBD0H@Z 6400f2c0 f i qftp.obj
 0001:0000e310       ?d_func@QFtp@@AAEPAVQFtpPrivate@@XZ 6400f310 f i qftp.obj
................ 

Обратите внимание здесь также есть
  информация от прилинкованных библиотек openssl. 
Это будет возможным если в network.pro
 подключить def файл :
DEF_FILE = $$PWD/../qt_openssl1_0_2_libs/ssl/ssleay32.def  # делает функции экпортируемыми.
Но так делать не надо! 
Не надо включать openssl в
 библиотеку QtNetWork.
Это нарушение прав и т.д.
Так делать нельзя, только для проверки : 
как будет выглядеть иоговый dll файл.


           111    _SSLv23_method
           112    _SSLv23_server_method
           113    _SSLv2_client_method
           114    _SSLv2_method
           115    _SSLv2_server_method
           116    _SSLv3_client_method
           117    _SSLv3_method
           118    _SSLv3_server_method
           314    _TLSv1_1_client_method
           313    _TLSv1_1_method
           315    _TLSv1_1_server_method
           341    _TLSv1_2_client_method
           350    _TLSv1_2_method
           343    _TLSv1_2_server_method
           172    _TLSv1_client_method
           170    _TLSv1_method
           171    _TLSv1_server_method
           119    _d2i_SSL_SESSION
           120    _i2d_SSL_SESSION

Но на самом деле кракозябры у имен функций QtNetWork связаны лишь с тем, что это с++ файлы, то есть имеют расширение cpp.

У файлов *.с кракозябр не будет. И это нормально и на экспортируемость не влияет.

А вот кусок файла map сборки libeay32.dll ( без DEF_FILE = libeay32.def ), когда собирается отдельно как самостоятельная библиотека, то есть как отдельный прнкт ssl.pro в составе src.pro:

Address         Publics by Value              Rva+Base       Lib:Object

 0000:00000000       __except_list              00000000     <absolute>
 0000:00000001       ___safe_se_handler_count   00000001     <absolute>
 0000:00009876       __ldused                   00009876     <absolute>
 0000:00009876       __fltused                  00009876     <absolute>
 0000:00000000       ___ImageBase               10000000     <linker-defined>
 0001:00009ca0       _OPENSSL_Uplink            1000aca0 f   uplink.obj
 0001:0000a290       _AES_wrap_key              1000b290 f   aes_wrap.obj
 0001:0000a2c0       _AES_unwrap_key            1000b2c0 f   aes_wrap.obj
 0001:0000a300       _AES_ofb128_encrypt        1000b300 f   aes_ofb.obj
 0001:0000a340       _AES_ige_encrypt           1000b340 f   aes_ige.obj
 0001:0000a980       _AES_bi_ige_encrypt        1000b980 f   aes_ige.obj
 0001:0000b0f0       _AES_ecb_encrypt           1000c0f0 f   aes_ecb.obj
 0001:0000b140       _AES_ctr128_encrypt        1000c140 f   aes_ctr.obj

В логе сборки openssl 1.0.2 штатно (из командной строки) видно, что есть присоединение def файла :

link /nologo /subsystem:console /opt:ref /debug /dll /out:out32dll\libeay32.dll /def:ms/LIBEAY32.def @C:\Users\p\AppData\Local\Temp\nm9634.tmp
....
link /nologo /subsystem:console /opt:ref /debug /dll /out:out32dll\ssleay32.dll /def:ms/SSLEAY32.def @C:\Users\p\AppData\Local\Temp\nmD31E.tmp

Или можно в Makefile сборки openssl увидеть то же :

$(O_SSL): $(SSLOBJ)
	$(LINK_CMD) $(MLFLAGS) /out:$(O_SSL) /def:ms/SSLEAY32.def @<<
  $(SHLIB_EX_OBJ) $(SSLOBJ)  $(L_CRYPTO) $(EX_LIBS)
<<
	IF EXIST $@.manifest mt -nologo -manifest $@.manifest -outputresource:$@;2


$(O_CRYPTO): $(CRYPTOOBJ)
	$(LINK_CMD) $(MLFLAGS) /out:$(O_CRYPTO) /def:ms/LIBEAY32.def @<<
  $(SHLIB_EX_OBJ) $(CRYPTOOBJ)  $(EX_LIBS)
<<
	IF EXIST $@.manifest mt -nologo -manifest $@.manifest -outputresource:$@;2

Все это означает, что надо подключать LIBEAY32.def и SSLEAY32.def в проект на Qt .

Может показаться, что библиотека libeay32.lib , создаваемая с libeay32.dll при сборке динамики, является такой же, что при сбрке статики. Но похоже это вообще разные вещи. Но название почему-то одно - lib.

Мы стандартно подключаем libeay32 и ssleay32 в файл проекта network.pro таким образом :

CONFIG(debug, debug|release){
    INCLUDEPATH += $$PWD/../qt_openssl1_0_2_libs
    LIBS += -L$$PWD/../../lib -llibeay32 -lssleay32
}

После сборки на Qt можно посмотреть содержание библиотек через dumpbin:

call "C:\Program Files (x86)\Microsoft Visual Studio 10.0\VC\vcvarsall.bat" x86
dumpbin.exe /exports libeay32.dll >  dumpbin_libeay32_dll.txt
dumpbin.exe /exports libeay32.lib >  dumpbin_libeay32_lib.txt

Для dll :

Microsoft (R) COFF/PE Dumper Version 10.00.30319.01
Copyright (C) Microsoft Corporation.  All rights reserved.

Dump of file libeay32.dll

File Type: DLL

  Section contains the following exports for LIBEAY32.dll

    00000000 characteristics
    623B1CEF time date stamp Wed Mar 23 16:13:19 2022
        0.00 version
           1 ordinal base
        4790 number of functions
        3700 number of names

    ordinal hint RVA      name

       1994    0 00001B09 ACCESS_DESCRIPTION_free = @ILT+2820(_ACCESS_DESCRIPTION_free)
       2751    1 00003B39 ACCESS_DESCRIPTION_it = @ILT+11060(_ACCESS_DESCRIPTION_it)
       1925    2 000041C9 ACCESS_DESCRIPTION_new = @ILT+12740(_ACCESS_DESCRIPTION_new)
       3860    3 000046C4 AES_bi_ige_encrypt = @ILT+14015(_AES_bi_ige_encrypt)
       3171    4 00002DFB AES_cbc_encrypt = @ILT+7670(_AES_cbc_encrypt)
       3217    5 00002EAA AES_cfb128_encrypt = @ILT+7845(_AES_cfb128_encrypt)
       3279    6 00005899 AES_cfb1_encrypt = @ILT+18580(_AES_cfb1_encrypt)
       3261    7 00004AC5 AES_cfb8_encrypt = @ILT+15040(_AES_cfb8_encrypt)

Для lib:

Microsoft (R) COFF/PE Dumper Version 10.00.30319.01
Copyright (C) Microsoft Corporation.  All rights reserved.

Dump of file libeay32.lib

File Type: LIBRARY

     Exports

       ordinal    name

          1994    _ACCESS_DESCRIPTION_free
          2751    _ACCESS_DESCRIPTION_it
          1925    _ACCESS_DESCRIPTION_new
          3860    _AES_bi_ige_encrypt
          3171    _AES_cbc_encrypt
          3217    _AES_cfb128_encrypt
          3279    _AES_cfb1_encrypt
          3261    _AES_cfb8_encrypt
          3216    _AES_ctr128_encrypt
          3040    _AES_decrypt
          2801    _AES_ecb_encrypt
          3033    _AES_encrypt

Может показаться , что здесь нет отладочной информации. Но на самом деле это не так. Отладочная информация здесь никак и не должна присутствовать. Это стандартная инфа с файла.

Можно поэкспирементировать с экспортом функций на примере одноц функции .

Сначала делаем префикс одной из функций проекта ssl.pro. Например для SSL_get_current_cipher, ставим префикс __declspec(dllexport). Кстати делаем это в заголовочном файле ssl.h , но он у нас в 2 местах в /openssl каталоге , который надо тащить с библиотеками типа lib , и в самом проекте ssl.pro. DEF_FILE убираем из проектов конечно же для чистоты эксперимента :

__declspec(dllexport) const SSL_CIPHER *SSL_get_current_cipher(const SSL *s);

Результат не замедляет себя ждать, dumpbin показывает изменения в ssleay32.dll и ssleay32.lib :


dll
    ordinal hint RVA      name
          1    0 00001078 SSL_get_current_cipher = @ILT+115(_SSL_get_current_cipher)

lib
     Exports
       ordinal    name
                  _SSL_get_current_cipher

If I had to guess, I'd say @ILT+270 is the synthesized name that would
allow to link to an exported function by ordinal

На следующем этапе мы попробуем объединить работу ssleay32.def файла , исключить из него одну функцию (например SSL_get_current_cipher) , в коде для SSL_get_current_cipher сделаем __declspec(dllexport) (как в примере ранее) и получим результат сборки в таком уже виде :

это кусок dumpbin export  ssleay32.lib:

            52    _SSL_get_cipher_list
            55    _SSL_get_ciphers
            56    _SSL_get_client_CA_list
                  _SSL_get_current_cipher
           272    _SSL_get_current_compression
           274    _SSL_get_current_expansion

это кусок dumpbin  export ssleay32.dll:

         55   B7 00001541 SSL_get_ciphers = @ILT+1340(_SSL_get_ciphers)
         56   B8 000016AE SSL_get_client_CA_list = @ILT+1705(_SSL_get_client_CA_list)
         14   B9 00001078 SSL_get_current_cipher = @ILT+115(_SSL_get_current_cipher)
        272   BA 00001087 SSL_get_current_compression = @ILT+130(_SSL_get_current_compression)
        274   BB 000016EF SSL_get_current_expansion = @ILT+1770(_SSL_get_current_expansion)

это кусок ssleay32.def:

    SSL_get_ciphers                         @55
    SSL_get_client_CA_list                  @56
    ;SSL_get_current_cipher                  @127
    SSL_get_current_compression             @272
    SSL_get_current_expansion               @274

В таком варианте network.pro соберется нормально :

   INCLUDEPATH += $$PWD/../qt_openssl1_0_2_libs
    LIBS += -L$$PWD/../../lib -llibeay32 -lssleay32
    DEF_FILE = $$PWD/../qt_openssl1_0_2_libs/ssl/ssleay32.def  # делает функции экпортируемыми !

Но отладчик в функции SSL_get_current_cipher не останавливается по-прежнему... То есть вроде бы отладчик останавливается , по F11 входим в любую функцию openssl , но по факту не видим кода этой функции и значениц переменных тоже и не понятно где мы вообще...

Примечание: как оказалось впоследствие, для отладки подключалась совсем не наша библиотека, а другая с таким же именем, но другой версии и совсем не отладочной сборки из каталога C:\WINDOWS.

Между делом приходим к пониманию зачем нужны _cdecl | __stdcall .... Декорирование имен (Name Decoration)

Изучение декорирование имен приводит к пониманию , почему файлы проекта network.pro собираются иначе , чем ssl.pro . Все дело в суффиксе компилируемых файлов cpp | с .

Calling convention extern "C" or .c file .cpp, .cxx or /TP
C naming convention (__cdecl) _test ?test@@ZAXXZ
Fast call naming convention (__fastcall) @test@0 ?test@@YIXXZ
Standard call naming convention (__stdcall) _test@0 ?test@@YGXXZ
Vector call naming convention (__vectorcall) test@@0 ?test@@YQXXZ

Вопрос отладки openssl трансформируется в промежуточный вопрос : можно ли отлаживать c файлы *.c, проходить по ним в отладчике?...

Делаем два отдельных проекта и изучаем данный вопрос.

extern "C" - Это для файлов cpp , которые будут использоваться в С проектах.
Exporting C++ Functions for Use in C-Language Executables

И поэтому наверное extern "C" не работает в файле *.с . Но нормально компилируется в *.cpp файле.

Exporting C Functions for Use in C or C++ Language Executables.

Чтобы использовать библиотеки написанные на С в проектах написанных на С++ надо
надо при подключении заголовочного файла такой С библиотеки оборачивать его таким образом:

extern "C" {
#include "old_c_file.h"
}

Иначе линковщик будет искать буквально int __cdecl old_c_file(double)" (?old_c_file@@YAHN@Z), а у нас в библиотеке _old_c_file:

main.obj:-1: ошибка: LNK2019: ссылка на неразрешенный внешний символ
"int __cdecl old_c_file(double)" (?old_c_file@@YAHN@Z) в функции _main

И вот примерно где-то в этот момент возникает подозрение - а может так и должно быть?

И никаким образом символы-кракозябры типа @@YAHN@Z для библиотек С и не должны там присутствовать. И отладка совсем не связана с этими кракозябрами?...

Если внимательно посмотреть какие фалы создаются при сборке библиотеки , то оказывается там есть и файл для отладки - называется PDB :

library.DLL – непосредственно код библиотеки, который необходим программам для исполнения
library.LIB – «импорт библиотеки», который описывает символы в разделяемой библиотеке. Этот файл генерируется только если DLL экспортирует какие-то символы; это файл необходим во время линков для всех, кто пользуется библиотекой
library.EXP – «файл экспорта» для компонуемой библиотеки, необходимый для линковки бинарников с циклическими зависимостями
library.ILK – генерируется если была использована опция /INCREMENTAL (нeобходимая для инкрементальной линковки), и содержит информацию, необходимую для дальнейшей инкрементальной компоновки.
library.PDB – генерируется, если была включена опция /DEBUG. Этот файл – база данных программы, которая содержит информацию для отладки библиотеки
library.MAP – генерируется, если была использована опция /MAP. Содержит информацию о внутренней структуре библиотеки/

pdb файлы - это файлы символов для отладчика . pdb файл создается для библиотеки если указана отладка.

Теперь смотрим исходники openssl на предмет наличия обертки extern "C" :

#ifdef  __cplusplus
extern "C" {
#endif

И находим их в большом количестве.