Как поймать непреднамеренную вставку функции? - PullRequest
6 голосов
/ 06 мая 2010

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

Пример, приведенный в книге, следующий:

my_source.c

mktemp() { ... }

main() {
  mktemp();
  getwd();
}

Libc

mktemp(){ ... }
getwd(){ ...; mktemp(); ... }

Согласно книге, в main() происходит то, что mktemp() (стандартная функция библиотеки C) вставляется реализацией в my_source.c . Хотя вызов main() для моей реализации mktemp() является намеренным поведением, * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * 10 ''. 1025 *

Очевидно, этот пример был реальной ошибкой, существовавшей в версии lpr в SunOS 4.0.3. Далее в книге объясняется, что исправлением было добавление ключевого слова static к определению mktemp() в my_source.c ; хотя смена имени в целом должна была исправить и эту проблему.

Эта глава оставляет мне несколько нерешенных вопросов, на которые, я надеюсь, вы, ребята, могли бы ответить:

  1. Есть ли в GCC способ предупредить вставку функции? Мы, конечно, никогда не собираемся делать это, и я хотел бы знать об этом, если это произойдет.
  2. Должна ли наша группа разработчиков использовать ключевое слово static перед всеми функциями, которые мы не хотим показывать?
  3. Может ли вставка произойти с функциями, представленными статическими библиотеками?

Спасибо за помощь.

EDIT

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

Ответы [ 6 ]

2 голосов
/ 06 мая 2010

Это действительно проблема компоновщика.

Когда вы компилируете кучу исходных файлов на Си, компилятор создаст объектный файл для каждого из них. Каждый файл .o будет содержать список открытых функций в этом модуле, а также список функций, которые вызываются кодом в модуле, но на самом деле там не определены, т.е. функции, которые этот модуль ожидает от какой-либо библиотеки.

Когда вы связываете кучу файлов .o вместе, чтобы создать исполняемый файл, компоновщик должен разрешить все эти пропущенные ссылки. Это точка, где может произойти вставка. Если есть неразрешенные ссылки на функцию с именем «mktemp» и несколько библиотек предоставляют открытую функцию с таким именем, какую версию следует использовать? Нет простого ответа на этот вопрос, и да, странные вещи могут случиться, если выбран неправильный

Так что да, в C хорошая идея "статизировать" все, если вам действительно не нужно использовать его из других исходных файлов. Фактически, во многих других языках это поведение по умолчанию, и вы должны пометить вещи как «общедоступные», если вы хотите, чтобы они были доступны извне.

1 голос
/ 06 мая 2010

Звучит так, как будто вы хотите, чтобы инструменты определяли наличие конфликтов имен в функциях, т. Е. Вы не хотите, чтобы внешние доступные имена функций случайно имели одно и то же имя и, следовательно, переопределяли или скрывали функции с тем же именем в библиотеке.

Недавно был задан вопрос SO, связанный с этой проблемой: Связывание библиотек с дублирующимися именами классов с использованием GCC

Использование опции --whole-archive во всех библиотеках, с которыми вы ссылаетесь, может помочь (но, как я уже упоминал в ответе, я действительно не знаю, насколько хорошо это работает или насколько легко убедить сборки в применении опция для всех библиотек)

1 голос
/ 06 мая 2010

Чисто формально, описание, которое вы описываете, является прямым нарушением правил определения языка C (правило ODR, на языке C ++). Любой достойный компилятор должен либо обнаруживать эти ситуации, либо предоставлять варианты для их обнаружения. Просто запрещается определять более одной функции с одним и тем же именем на языке C, независимо от того, где определены эти функции (Стандартная библиотека, другая пользовательская библиотека и т. Д.)

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

Итак, опять же, вам не нужно беспокоиться о вставке, если в ваших библиотеках нет слабых символов. Если вы это сделаете (или если вы подозреваете, что это так), вы должны обратиться к документации вашего компилятора, чтобы узнать, предлагает ли он вам средства для проверки слабого разрешения символов.

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

0 голосов
/ 14 июля 2016

Чередование происходит, когда компоновщик пытается связать отдельные модули. Это не может происходить внутри модуля. Если в модуле есть повторяющиеся символы, компоновщик сообщит об этом как об ошибке.

Для * nix-линкеров непреднамеренное добавление является проблемой, и компоновщику трудно защититься от него. Для целей этого ответа рассмотрим два этапа связывания:

  1. Компоновщик связывает единицы перевода в модули (в основном приложения или библиотеки).
  2. Компоновщик связывает все оставшиеся необнаруженные символы путем поиска в модулях.

Рассмотрим сценарий, описанный в «Программирование на Expert C» и вопрос SiegeX. Кулак компоновщика пытается собрать модуль приложения. Это значит, что символ mktemp () является внешним и пытается найти определение функции для символа. Компоновщик находит определение функции в объектном коде модуля приложения и помечает символ как найденный. На этом этапе символ mktemp () полностью разрешен. Это не считается каким-либо предварительным, чтобы позволить для возможности, что модуль anothere мог бы определить символ. Во многих отношениях это имеет смысл, поскольку компоновщик должен сначала попытаться разрешить внешние символы в модуле. в настоящее время ссылки. Это только необнаруженные символы, которые он ищет при связывании в других модулях. Кроме того, поскольку символ был помечен как разрешенный, компоновщик будет использовать приложения mktemp () в любом другие случаи, когда необходимо разрешить этот символ. Таким образом, версия приложения mktemp () будет использоваться библиотекой.

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

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

0 голосов
/ 07 мая 2010

Относительно легко написать скрипт, который запускает nm -o для всех ваших файлов .o и ваших библиотек и проверяет, определено ли внешнее имя как в вашей программе, так и в библиотеке. Просто одна из многих здравомыслящих служб, которые не предоставляет компоновщик Unix, потому что он застрял в 1974 году, просматривая по одному файлу за раз. (Попробуйте разместить библиотеки в неправильном порядке и посмотрите, не получите ли вы полезное сообщение об ошибке!)

0 голосов
/ 06 мая 2010

Если к функции не требуется доступ за пределами C-файла, в котором она находится, то да, я бы рекомендовал сделать функцию static.

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

...