вставлять строку через заголовок, который нельзя оптимизировать - PullRequest
10 голосов
/ 11 апреля 2019

При разработке библиотеки только для заголовков я хотел бы убедиться, что данная строка встроена во все двоичные файлы, использующие мой заголовок, даже если компилятор настроен на оптимизацию удаления неиспользуемых констант, а двоичный файл удаляется.

У встраивания не должно быть побочных эффектов (кроме увеличения получаемого двоичного кода).

Я не знаю, как люди будут использовать заголовки, но

  • заголовки могут быть включены в несколько модулей компиляции, все они объединены в один двоичный файл
  • целевыми платформами являются Linux / macOS / Windows
  • компиляторами, скорее всего, будут gcc / clang / MSVC

Моя тривиальная попытка составляет:

static char frobnozzel_version_string[] = "Frobnozzel v0.1; © 2019 ACME; GPLv3";

... но это get легко удаляется во время сборки (поскольку строка фактически нигде не используется, она легко становится жертвой оптимизирующего компилятора).

Таким образом, вопрос заключается в следующем: возможно ли встраивать строку в любой двоичный файл, содержащий заданный заголовок, который не будет оптимизирован или исключен обычными стратегиями для создания двоичных файлов «Release»?

Я знаю, что любой, кто использует библиотеку, может просто (вручную) удалить все, что я вставил, но давайте предположим, что люди просто используют заголовок "как есть".


Контекст: рассматриваемые заголовки выпускаются по GPL , и я хотел бы иметь возможность проверить, действительно ли пользователи соблюдают лицензию.

Ответы [ 4 ]

3 голосов
/ 11 июля 2019

Вы можете встроить псевдооперации сборки в заголовок, и он должен остаться (хотя он никогда не используется):

asm(".ascii \"Frobnozzel v0.1; © 2019 ACME; GPLv3\"\n\t");

Обратите внимание, что это GCC / Clang-специфично.

Альтернативой для MSVC будет использование #pragma comment или __asm db:

__asm db "Frobnozzel v0.1; © 2019 ACME; GPLv3"
#pragma comment(user, "Frobnozzel v0.1; © 2019 ACME; GPLv3")

Вот пример:

chronos@localhost ~/Downloads $ cat file.c 
#include <stdio.h>

#include "file.h"

int main(void)
{
        puts("The string is never used.");
}
chronos@localhost ~/Downloads $ cat file.h
#ifndef FILE_H
#define FILE_H 1

#if defined(__GNUC__)
    asm(".ascii \"Frobnozzel v0.1; © 2019 ACME; GPLv3\"\n\t");
#elif defined(_MSC_VER)
# if defined(_WIN32)
    __asm db "Frobnozzel v0.1; © 2019 ACME; GPLv3"
# elif defined(_WIN64)
#  pragma comment(user, "Frobnozzel v0.1; © 2019 ACME; GPLv3")
# endif
#endif
chronos@localhost ~/Downloads $ gcc file.c
chronos@localhost ~/Downloads $ grep "Frobnozzel v0.1; © 2019 ACME; GPLv3" a.out
Binary file a.out matches
chronos@localhost ~/Downloads $ 

Замените команду gcc на clang и результатто же самое.

Для 64-битной Windows это требует либо замены user устаревшей exestr, либо создания файла ресурсов, который встраивает строку в исполняемый файл.Так как это, строка будет удалена при связывании.

2 голосов
/ 10 июля 2019

TL; DR;

Возможно, вам не удастся принудительно ввести значение в единицу компиляции, но вы можете применить символ, определив глобальную переменную в заголовке. т.е.: long using_my_library_version_1_2_3;

Символ будет доступен извне в конечном двоичном файле и может быть проверен (хотя, как и любое решение, его можно обойти, не говоря уже о том, что сам заголовок можно изменить).

РЕДАКТИРОВАТЬ : Чтобы уточнить (из-за комментариев), не используйте переменную static.

При использовании глобальной переменной по умолчанию она будет extern и не будет оптимизирована (в случае, если другие объекты, загружающие двоичный файл, используют идентификатор).

Предостережения и пример:

Как уже упоминалось в комментариях, идентификатор (имя) глобальной переменной - это строка в этом подходе.

Однако при компиляции исполняемых файлов (и ядер) идентификаторы могут быть удалены из окончательного двоичного файла при компиляции с (-s). Это часто делают разработчики встроенных систем и люди, которым нравится делать отладку живым адом (даже больше, чем в любом случае).

Быстрый пример:

// main.c
int this_is_example_version_0_0_1; /* variable name will show in the file */

int main(void) {
  /* placed anywhere to avoid the "not used" warning: */
  (void)this_is_example_version_0_0_1;
  return 0;
}

// extra.c
int this_is_example_version_0_0_1; /* repeat line to your heart's content  */
int this_is_example_version_0_0_1; /* (i.e., if header has no include guard) */

Компиляция:

 $ cc -xc -o a -Wall -O2 main.c extra.c

Список всех идентификаторов / имен (будет отображаться глобально):

 nm ./a | grep "this_is_example_version"

Проверка строки в двоичном файле с использованием:

$ grep -F "this_is_example_version" ./a

подробности:

Забавные факты о C, которые делают возможным это решение ...:

  1. C определяет extern как значение по умолчанию для объявлений функций и переменных в глобальной области (6.2.2, подраздел 5).

  2. В соответствии с разделом 6.2.2 («Связи идентификаторов») «каждое объявление определенного идентификатора с внешней связью обозначает один и тот же объект или функцию».

    Это означает, что дубликаты объявлений в глобальной области видимости будут сопоставляться с одной декларацией.

  3. Объявления переменных и определения переменных выглядят одинаково, когда переменная помещается в глобальную область и все ее биты установлены в ноль.

    Это потому, что глобальные переменные по умолчанию инициализируются нулями. Следовательно, компиляторы не могут определить, является ли int foo; определением (int foo = 0;) или объявлением (extern int foo;).

Из-за этой "идентичности" и этих правил компиляторы преобразуют неоднозначные объявления / определения глобальных переменных в "слабые" объявления, которые должны разрешаться компоновщиком.

Это означает, что если вы определяете глобальную переменную без ключевого слова extern и без значения, неоднозначное объявление / определение заставит компилятор выдавать слабый символ, который будет выставлен в окончательном двоичном файле.

Этот символ может использоваться для идентификации того факта, что заголовок использовался где-то в программе.

0 голосов
/ 10 июля 2019

Я не знаю, есть ли какой-нибудь стандартный способ сделать это, но в зависимости от того, как работает ваша библиотека, у меня может быть разумное решение.Многие библиотеки имеют функции инициализации, которые обычно вызываются только один раз или, по крайней мере, очень редко в коде.srand() является одним из примеров.

Вам может потребоваться функция init для работы вашей библиотеки, и, не указывая точно ее назначение, вы можете просто сказать, что главной функции должна быть строка initlib(); перед любойбиблиотечные функции используются.Вот пример:

lh:

// Macro disguised as a function
#define initlib() init("Frobnozzel v0.1; © 2019 ACME; GPLv");  

void init(const char *);
void libfunc(void);

lc:

#include "l.h"
#include <string.h>
#include <stdlib.h>

int initialized = 0;

void init(const char *str) {
    if(strcmp(str, "Frobnozzel v0.1; © 2019 ACME; GPLv3") == 0)
        initialized = 1;
}

void libfunc(void) {
    if(!initialized)
        exit(EXIT_FAILURE);
    /* Do stuff */
}

Примечание: Я знаю, что вы просили только заголовок,но принцип тот же.И, наконец, преобразование пары .h, .c в файл .h является самой простой задачей в мире.

Если вы используете библиотечную функцию libfunc до того, как использовали макрос инициализации initlib, программа просто закроется.То же самое произойдет, если в файле заголовка будет изменена строка об авторских правах.

Конечно, обойти это несложно, если хотите, но это работает.

Для тестирования я использовалэтот код:

int main()
{
    initlib();
    libfunc();
    printf("Hello, World!\n");
}

Я попробовал это, скомпилировав l.c в общую библиотеку.Затем я скомпилировал простую основную программу с clang и gcc, используя -O3.Двоичные файлы работали так, как должны, и содержали строку с авторскими правами.

0 голосов
/ 10 июля 2019

Одно важное правило: заголовок не должен выделять память.Это означает, что заголовок:

  • не должен объявлять переменные (если только не префикс extern);
  • не должен определять функции (т.е. не должен генерировать код);

Ваше утверждение:

static char frobnozzel_version_string[] = "Frobnozzel v0.1; © 2019 ACME; GPLv3";

противоречит упомянутым правилам.


, но это легко удаляется во время сборки

Это не худшее, что может случиться, на самом деле.Хуже всего будет, когда более 2 блоков компиляции (файлы .c) включают этот заголовок.У каждого из этих модулей компиляции будет своя переменная с именем frobnozzel_version_string.Если компилятор не будет жаловаться на разные переменные с одинаковым именем, выполнение операций с указанным указателем приведет к неожиданным результатам, поскольку вы не будете точно знать, с какой из переменных вы работаете.


возможно ли встроить строку в любой двоичный файл, который включает данный заголовок, который не будет оптимизирован или исключен обычными стратегиями для создания двоичных файлов "Release"?

Если вы простохотите сделать строку видимой из любого места (при условии, что файл заголовка включен), просто используйте:

#define FROBNOZZEL_VERSION_STRING ("Frobnozzel v0.1; © 2019 ACME; GPLv3")

Он не выделяет память, его нельзя использовать неоднозначно ...


Комментарий от @ klutt позволил мне лучше понять вопрос:

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

Таким образом, вы в основном хотите «вставить» некоторые данные во все блоки компиляции, которые включают этот заголовок.

Чтобы компилятор не удалял данные, вы должны использовать volatile.

Как вы его используете, это другой вопрос.Я вижу только несколько альтернатив для этого:

  1. Только заголовок: вы нарушите правила наилучшей практики (как я уже упоминал выше).У вас будет много копий одних и тех же данных, но пока они не будут использоваться, это не должно быть проблемой.

заголовочный файл

static volatile char frobnozzel_version_string[] = "Frobnozzel v0.1; © 2019 ACME; GPLv3";
Предоставьте библиотеку кода + заголовок: вы будете следовать лучшим рекомендациям за счет доставки двух файлов.

.c file

static volatile char frobnozzel_version_string[] = "Frobnozzel v0.1; © 2019 ACME; GPLv3";

заголовочный файл

extern static volatile char frobnozzel_version_string[]

Заказ из static и volatile не важен.

...