Можно ли связать некоторые функции в обычном бинарном файле? - PullRequest
4 голосов
/ 15 января 2012

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

Еще немного информации: file1: test.c

#include <stdio.h>

int add(int x,int y)
{
    return x+y;
}

int main(int argc, const char *argv[])
{
    printf("%d\n",add(3,4));
    return 0;
}

file2: test1.c

#include <stdio.h>

int main(int argc, const char *argv[]) 
{
    printf("%d\n",add(8,8));
    return 0; 
}

gcc test.c -o test.exe
gcc test1.c test.exe -o test1.exe

Выход:

ld: in test.exe, can't link with a main executable
collect2: ld returned 1 exit status

Ответы [ 3 ]

2 голосов
/ 15 января 2012

Боюсь, что нет.

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

Вы можете провести простой эксперимент, чтобы выяснить различия, вот программа, которая выводит «Hello World»:

// main.c
#include <stdio.h>

int main()
{
    printf("Hello World!");
    return 0;
}

Используя gcc -c, вы можете скомпилировать исходный код в перемещаемый объект:

$ gcc -c main.o

$ readelf -s main.o

Symbol table '.symtab' contains 10 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 00000000     0 FILE    LOCAL  DEFAULT  ABS main.c
     2: 00000000     0 SECTION LOCAL  DEFAULT    1 
     3: 00000000     0 SECTION LOCAL  DEFAULT    3 
     4: 00000000     0 SECTION LOCAL  DEFAULT    4 
     5: 00000000     0 SECTION LOCAL  DEFAULT    5 
     6: 00000000     0 SECTION LOCAL  DEFAULT    7 
     7: 00000000     0 SECTION LOCAL  DEFAULT    6 
     8: 00000000    29 FUNC    GLOBAL DEFAULT    1 main
     9: 00000000     0 NOTYPE  GLOBAL DEFAULT  UND printf

Отсюда видно, что значение функции main равно 0x0, что означает, что она еще не перемещена и может быть связана с другими.

Но когда вы компилируете файл с помощью команды gcc, для генерации исполняемого файла:

$ gcc main.c
$ readelf -s a.out | grep main
     2: 00000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.0 (2)
    39: 00000000     0 FILE    LOCAL  DEFAULT  ABS main.c
    51: 00000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@@GLIBC_
    62: 080483c4    29 FUNC    GLOBAL DEFAULT   13 main

Теперь вы можете видеть, что адрес функции main был перемещен в 0x80483c4, который является адресом времени выполнениякода функции.Генерирование a.out больше не может быть связано с другими, так как это может привести к нарушению адреса времени выполнения.

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

Для получения более подробной информации, я предлагаю вам прочитать главу «Связывание» в книге Компьютерная система: перспективы программиста , в которой много говорится о связывании и перемещении.

1 голос
/ 15 января 2012

С практической точки зрения существует небольшая разница между файлом объекта (.o) и исполняемым файлом. Объектный файл может содержать несвязанные символы, а исполняемый файл не может. Исполняемый файл должен содержать точку входа, в которой объектный файл не имеет такого ограничения. Исполняемый файл имеет более полный заголовок. В исполняемом файле также разрешены все смещения перехода, как это было на этапе разрешения связывания. Некоторые функции могли быть постоянно отключены.

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

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

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

1 голос
/ 15 января 2012

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

Предполагается, однако, что функции в объектном файле, который вы выгрузили, следуют ABI и соглашениям о вызовах для платформы / компилятора, которую вы ''мы работаем с (я знаю, что это кажется очевидным), и он не может включать свою собственную точку входа (то есть, функцию main()).Что касается второго пункта, объектный файл должен быть в основном «библиотекой» автономных функций.Это означает, что вы не можете ссылаться на исполняемый файл.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...