tl; др. Точка входа (вероятно) называется ZCMain_main_closure
, и это структура данных, которая ссылается на блок кода, а не на сам код. Тем не менее, он интерпретируется средой выполнения Haskell и напрямую соответствует «значению» Haskell функции main :: IO ()
в вашей программе main.hs
.
Более длинный ответ включает в себя больше, чем вы когда-либо хотели знать о связывании программ, но вот в чем дело. Когда вы берете программу на C, как:
#include <stdio.h>
int main()
{
printf("I like C!\n");
}
скомпилируйте его в объектный файл с помощью gcc
:
$ gcc -Wall -c hello.c
и проверьте таблицу символов объектного файла:
$ nm hello.o
0000000000000000 T main
U printf
вы увидите, что оно содержит определение символа main
и (неопределенную) ссылку на внешний символ printf
.
Теперь вы можете представить, что main
является «точкой входа» этой программы. Ха ха ха ха Какая наивная и глупая вещь для тебя!
На самом деле настоящие гуру Linux знают, что точка входа в вашу программу вообще не находится в объектном файле hello.o
. Где это находится? Ну, это в "C runtime" , маленьком файле, который связывается с gcc
, когда вы фактически создаете свой исполняемый файл:
$ nm /usr/lib/x86_64-linux-gnu/crt1.o
0000000000000000 D __data_start
0000000000000000 W data_start
0000000000000000 R _IO_stdin_used
U __libc_csu_fini
U __libc_csu_init
U __libc_start_main
U main
0000000000000000 T _start
$
Обратите внимание, что этот объектный файл имеет неопределенную ссылку на main
, которая будет связана с вашей так называемой точкой входа в hello.o
. Именно эта маленькая заглушка определяет настоящую точку входа, а именно _start
. Вы можете сказать, что это фактическая точка входа, потому что если вы свяжете программу с исполняемым файлом, вы увидите, что расположение символа _start
и точка входа ELF (это адрес, на который ядро фактически передает управление первым). когда вы execve()
ваша программа) совпадете:
$ gcc -o hello hello.o
$ nm hello | egrep 'T _start'
0000000000400430 T _start
$ readelf -h hello | egrep Entry
Entry point address: 0x400430
Все это означает, что «точка входа» программы - это довольно сложная концепция.
Когда вы компилируете и запускаете программу на C с помощью цепочки инструментов LLVM вместо GCC, ситуация довольно похожа. Это сделано для того, чтобы все было совместимо с GCC. Так называемая точка входа в вашем файле hello.ll
- это просто функция C main
, а не точка входа real вашей программы. Это все еще обеспечивается заглушкой crt1.o
.
Теперь, если мы (наконец) перейдем от разговора о C к разговору о Haskell, среда выполнения Haskell, очевидно, примерно в миллиард раз сложнее среды выполнения C, но она построена поверх среды выполнения C. Итак, когда вы компилируете программу на Haskell обычным способом:
$ ghc main.hs
stack ghc -- main.hs
[1 of 1] Compiling Main ( main.hs, main.o )
Linking main ...
$
вы можете видеть, что исполняемый файл имеет точку входа с именем _start
:
$ nm main | egrep 'T _start'
0000000000406560 T _start
, которая на самом деле является той же заглушкой среды выполнения C, что и раньше, вызывает точку входа C:
$ nm main | egrep 'T main'
0000000000406dc4 T main
$
но это main
не ваш Haskell main
. Эта main
является функцией C main
в программе, динамически создаваемой GHC во время соединения. Вы можете посмотреть на такую программу, запустив:
$ ghc -v -keep-tmp-files -fforce-recomp main.hs
и поиск файла с именем ghc_4.c
где-то в подкаталоге /tmp
:
$ cat /tmp/ghc10915_0/ghc_4.c
#include "Rts.h"
extern StgClosure ZCMain_main_closure;
int main(int argc, char *argv[])
{
RtsConfig __conf = defaultRtsConfig;
__conf.rts_opts_enabled = RtsOptsSafeOnly;
__conf.rts_opts_suggestions = true;
__conf.rts_hs_main = true;
return hs_main(argc,argv,&ZCMain_main_closure,__conf);
}
Теперь, вы видите эту внешнюю ссылку на ZCMain_main_closure
? Верьте или нет, это точка входа в Haskell для вашей программы, и вы должны найти ее в main.o
, независимо от того, скомпилированы ли вы с помощью ванильного конвейера GHC или через бэкэнд LLVM:
$ egrep ZCMain_main_closure main.ll
%ZCMain_main_closure_struct = type <{i64, i64, i64, i64}>
...
Теперь это не «функция». Это специально отформатированная структура данных (замыкание), которую понимает система времени исполнения Haskell. Вышеуказанная функция * 1082 (еще одна точка входа!) Является основной точкой входа в среду выполнения Haskell:
$ nm ~/.stack/programs/x86_64-linux/ghc-8.4.3/lib/ghc-8.4.3/rts/libHSrts.a | egrep hs_main
0000000000000000 T hs_main
$
и он принимает закрытие для главной функции Haskell в качестве точки входа в Haskell, чтобы начать выполнение вашей программы.
Итак, если вы прошли через все эти неприятности в надежде изолировать программу на Haskell в файле *.ll
, которую вы могли бы каким-то образом запустить непосредственно, перейдя к ее точке входа, то у меня для вас есть плохие новости. ..;)