Как вы отметили, ядро не передает исполняемый двоичный файл в качестве пути к интерпретатору:
$ /lib64/ld-linux-x86-64.so.2 /proc/self/exe
loader cannot load itself
Хотя компоновщик glib c Dynami c поддерживает этот метод вызова (при условии программа для запуска в качестве аргумента), это не то, что используется во время нормального выполнения интерпретируемого двоичного файла ELF. Фактически, ядро предоставляет аргументы от execve в неизмененном виде для компоновщика Dynami c.
Компоновщик Dynami c вообще не «загружает» и не «выполняет» интерпретируемый двоичный файл ELF. Ядро загружает и интерпретатор и интерпретируемый двоичный файл в память и начинает выполнение с точки входа интерпретатора. Точка входа интерпретируемого двоичного файла передается интерпретатору через поле AT_ENTRY
во вспомогательном векторе.
Компоновщик динамического c затем выполняет необходимое связывание времени выполнения и переходит к «реальной» точке входа .
Вы можете наблюдать все это в gdb, если вы установите точку останова на _start при выполнении обычного интерпретируемого исполняемого файла ELF. С помощью «show args» вы увидите «настоящий» argv без каких-либо дополнительных значений, а в карте памяти процесса уже будет загружен интерпретируемый двоичный файл (до того, как интерпретатор выполнит одну инструкцию).
#!
скрипты работают так, как вы ожидаете (фактически манипулируя значениями argv).