이 난독 화 된 C 코드는 main ()없이 실행된다고 주장하지만 실제로 어떤 역할을합니까?


84
#include <stdio.h>
#define decode(s,t,u,m,p,e,d) m##s##u##t
#define begin decode(a,n,i,m,a,t,e)

int begin()
{
    printf("Ha HA see how it is?? ");
}

이것은 간접적으로 호출 main합니까? 어떻게?


146
정의 된 매크로는 "main"으로 시작합니다. 그것은 단지 속임수입니다. 흥미로운 것은 없습니다.
rghome

10
귀하의 툴체인은 파일에 주변의 전처리 코드를 떠날 수있는 옵션이 있어야합니다 - 컴파일 실제 파일 - 당신이 그것을 볼 곳을, 참으로, 주 ()가

@rghome 답변으로 게시하지 않으시겠습니까? 그리고 찬성 투표 수를 고려할 때 분명히 흥미 롭습니다.
Matsemann 2016

3
@Matsemann 와우! 나는 찬성 투표를 보지 못했습니다. 답변으로 변경할 수 있으며 댓글에 찬성표가 답변 찬성표라면 단연 내 최고 점수이지만 이미 자세한 답변이 있습니다. 내 의견의 요점은 그다지 흥미롭지 않기 때문에 답변에 찬성하고 싶지 않은 사람들에게 대안으로 작용한다는 것입니다. 그래도 지적 해 주셔서 감사합니다.
rghome

여러분, 언어 자체가 아니라 진입 점을 설정하는 운영 체제 도구로서 링커에 달려 있습니다. 자체 진입 점을 설정할 수도 있고 실행 가능한 라이브러리를 만들 수도 있습니다! unix.stackexchange.com/a/223415/37799
Ho1

답변:


193

C 언어는 독립형호스트 형의 두 가지 범주로 실행 환경을 정의합니다 . 두 실행 환경 모두에서 프로그램 시작을 위해 환경에 의해 함수가 호출됩니다.
A의 자립 환경 프로그램 기동 기능 구현 중에 정의 될 수 호스팅 환경이 있어야한다 main. 정의 된 환경에서 프로그램 시작 기능 없이는 C 프로그램을 실행할 수 없습니다.

귀하의 경우 main에는 전 처리기 정의에 의해 숨겨져 있습니다. begin()에 확장됩니다 decode(a,n,i,m,a,t,e)더 확장 될 것이다 main.

int begin() -> int decode(a,n,i,m,a,t,e)() -> int m##a##i##n() -> int main() 

decode(s,t,u,m,p,e,d)7 개의 매개 변수가있는 매개 변수화 된 매크로입니다. 이 매크로의 대체 목록은 m##s##u##t입니다. m, s, ut(4)이다 , 1 , 3 번째 및 2 교체리스트에 사용되는 파라미터.

s, t, u, m, p, e, d
1  2  3  4  5  6  7

나머지는 소용이 없습니다 ( 단지 난독 화하기 위해 ). 에 전달 인자 decode"이다 , N , I는 , m이 때문에, 식별자, A, t, E는" 과는 인수로 대체 하고 각각.m, s, utm, a, in

 m --> m  
 s --> a 
 u --> i 
 t --> n

11
@GrijeshChauhan 모든 C 컴파일러는 매크로를 처리하며 C89 이후 모든 C 표준에서 필요합니다.
jdarthenay

17
그것은 명백히 잘못된 것입니다. Linux에서는 _start(). 또는 더 낮은 수준에서 부팅 후 IP가 설정된 주소와 프로그램의 시작을 정렬 할 수 있습니다. main()C 표준 라이브러리 입니다. C 자체는 이에 제한을 두지 않습니다.
ljrk

1
@haccks 표준 라이브러리 는 진입 점을 정의합니다. 언어 자체는 상관하지 않습니다
ljrk

3
어떻게 decode(a,n,i,m,a,t,e)될지 설명해 주 m##a##i##n시겠습니까? 문자를 대체합니까? decode함수 문서에 대한 링크를 제공 할 수 있습니까 ? 감사.
AL

1
@AL First begin는 이전에 정의 된 것으로 대체되도록 decode(a,n,i,m,a,t,e)정의됩니다. 이 함수는 인수 s,t,u,m,p,e,d를 받아이 형식으로 연결합니다 m##s##u##t( ##연결을 의미합니다). 즉, p, e 및 d의 값을 무시합니다. 당신이 "전화"로 decodeS = A, t = N, U 난을 =와, m = m 효과적으로 대체 begin와 함께 main.
ljrk

71

사용 시도 gcc -E source.c와 함께, 출력단을 :

int main()
{
    printf("Ha HA see how it is?? ");
}

따라서 main()함수는 실제로 전처리기에 의해 생성됩니다.


37

문제의 프로그램은 수행 전화를 main()인해 매크로 확장에, 그러나 당신의 가정은 결함이 - 그것은 하지 않습니다 호출 할 필요가 main()전혀!

엄밀히 말하면, C 프로그램을 가지고 main심볼 없이 컴파일 할 수 있습니다 . mainc library자체 초기화를 마친 후 점프 할 것으로 예상되는 것입니다 . 일반적으로 mainlibc 기호에서 _start. main없이 어셈블리를 실행하는 매우 유효한 프로그램을 항상 가질 수 있습니다. 이것 좀보세요 :

/* This must be compiled with the flag -nostdlib because otherwise the
 * linker will complain about multiple definitions of the symbol _start
 * (one here and one in glibc) and a missing reference to symbol main
 * (that the libc expects to be linked against).
 */

void
_start ()
{
    /* calling the write system call, with the arguments in this order:
     * 1. the stdout file descriptor
     * 2. the buffer we want to print (Here it's just a string literal).
     * 3. the amount of bytes we want to write.
     */
    asm ("int $0x80"::"a"(4), "b"(1), "c"("Hello world!\n"), "d"(13));
    asm ("int $0x80"::"a"(1), "b"(0)); /* calling exit syscall, with the argument to be 0 */
}

위를로 컴파일하고 인라인 어셈블리에서 시스템 호출 (인터럽트)을 실행하여 화면에 gcc -nostdlib without_main.c인쇄 Hello World!되는 것을 확인하십시오 .

이 특정 문제에 대한 자세한 내용은 ksplice 블로그를 확인하십시오.

또 다른 흥미로운 문제 main는 C 함수에 해당 하는 기호 없이 컴파일되는 프로그램을 가질 수도 있다는 것 입니다. 예를 들어 다음을 매우 유효한 C 프로그램으로 사용할 수 있으며 경고 수준이 올라갈 때만 컴파일러가 우는 소리를냅니다.

/* These values are extracted from the decimal representation of the instructions
 * of a hello world program written in asm, that gdb provides.
 */
const int main[] = {
    -443987883, 440, 113408, -1922629632,
    4149, 899584, 84869120, 15544,
    266023168, 1818576901, 1461743468, 1684828783,
    -1017312735
};

배열의 값은 Hello World를 화면에 인쇄하는 데 필요한 지침에 해당하는 바이트입니다. 이 특정 프로그램의 작동 방식에 대한 자세한 설명은이 블로그 게시물을 참조 하십시오.

이 프로그램에 대해 마지막으로 알려 드리고 싶습니다. C 언어 사양에 따라 유효한 C 프로그램으로 등록되어 있는지는 모르겠지만, 사양 자체를 위반하더라도 컴파일하고 실행하는 것은 확실히 가능합니다.


1
_start정의 된 표준 의 일부 이름입니까 , 아니면 구현에 특정한 것입니까? 확실히 "배열로서의 메인"은 아키텍처에 따라 다릅니다. 또한 보안 제한으로 인해 런타임에 "배열로서의 기본"트릭이 실패하는 것은 부당하지 않을 것입니다 (정규자를 사용하지 않고 const여전히 많은 시스템에서이를 허용하는 경우 더 많음).
mah

1
@mah : _start는 AMD64 psABI가에 대한 참조 포함 불구하고, ELF 표준에없는 _start에서 3.4 프로세스 초기화 . 공식적으로 ELF e_entry는 ELF 헤더 의 주소 만 알고 _start있으며 구현에서 선택한 이름 일뿐입니다.
ninjalj

1
@mah 또한 중요한 것은 보안 제한으로 인해 런타임에 "배열로서의 기본"트릭이 실패하는 것이 부당하지 않을 것입니다 (const 한정자를 사용하지 않은 경우 더 가능성이 높지만 여전히 많은 시스템에서 허용 할 수 있음). 그것). 최종 실행 파일이 어떤 식 으로든 안전하지 않은 것으로 구별 될 수있는 경우에만-바이너리 실행 파일은 어떻게 거기에 도착했는지에 관계없이 바이너리 실행 파일입니다. 그리고 const이진 실행 파일의 기호 이름은 main. 그 이상도 이하도 아닌. const실행 시간에 아무 의미가없는 C 구조입니다.
앤드류 헨레

1
@Stewart : 확실히 ARMv6l에서 실패합니다 (세그먼트 오류). 그러나 모든 x86-64 아키텍처에서 작동합니다.
leftaroundabout

@AndrewHenle 바이너리 실행 파일은 그것이 어떻게 거기에 있든 상관없이 바이너리 실행 파일 입니다. 이진 실행 파일은 실행 가능한 명령의 단일 blob이 아니라 신중하게 매핑 된 파티션의 blob이며, 일부는 명령이고, 일부는 읽기 전용 데이터이며, 일부는 읽기-쓰기 데이터로 초기화되는 데이터입니다. (일부) 보안 하드웨어 MMU는 그렇게 표시되지 않은 페이지에서의 실행을 방지 할 수 있으며, 예를 들어 스택 오버플로로 인해 스택에서 코드를 실행하는 것을 방지하는 좋은 기능이지만 슬프게도 때때로 합법적이거나 종종 활성화되지 않습니다.
mah

30

누군가 마술사처럼 행동하려고합니다. 그는 우리를 속일 수 있다고 생각합니다. 그러나 우리 모두는 c 프로그램 실행이 main().

int begin()대체 될 decode(a,n,i,m,a,t,e)전처리 단계 중 하나 개를 통과하여. 그런 다음 다시 decode(a,n,i,m,a,t,e)m ## a ## i ## n으로 대체됩니다. 매크로 호출의 위치 연관과 마찬가지로 swill의 값은 character a입니다. 마찬가지로 u'i' t로 대체되고 'n'으로 대체됩니다. 그리고 그렇게 m##s##u##t될 것입니다.main

관한 ##매크로 팽창 기호는 전처리 연산자이며 토큰 붙여 넣기를 행한다. 매크로가 확장되면 각 '##'연산자의 양쪽에있는 두 개의 토큰이 단일 토큰으로 결합 된 다음 매크로 확장에서 '##'및 두 개의 원래 토큰을 대체합니다.

나를 믿지 않는다면 -E플래그로 코드를 컴파일 할 수 있습니다 . 전처리 후 컴파일 과정이 중단되고 토큰 붙여 넣기 결과를 볼 수 있습니다.

gcc -E FILENAME.c

11

decode(a,b,c,d,[...])처음 4 개의 인수를 섞고 결합하여 순서대로 새 식별자를 얻습니다 dacb. (나머지 세 개의 인수는 무시됩니다.) 예를 들어, decode(a,n,i,m,[...])식별자를 제공합니다 main. 이것이 begin매크로가 정의 된 것입니다.

따라서 begin매크로는 간단히 main.


2

귀하의 예제에서는 컴파일러가 매크로로 대체하고 차례로 m ## s ## u ## t 표현식으로 대체되는 매크로 main()이므로 함수가 실제로 존재 begin합니다 decode. 매크로 확장을 사용하면 from ##이라는 단어에 도달하게 main됩니다 decode. 이것은 추적입니다.

begin --> decode(a,n,i,m,a,t,e) --> m##parameter1##parameter3##parameter2 ---> main

를 갖는 것은 단지 속임수 main()이지만 main()C 프로그래밍 언어에서는 프로그램의 입력 함수에 이름 을 사용할 필요가 없습니다. 운영 체제와 해당 도구 중 하나 인 링커에 따라 다릅니다.

Windows에서 항상 사용하지 않는 main()하지만, 오히려 WinMainwWinMain 있지만, 사용할 수있는 main(), 심지어 마이크로 소프트의 툴 체인과 함께 . Linux에서는 _start.

언어 자체가 아니라 진입 점을 설정하는 운영 체제 도구로서의 링커에 달려 있습니다. 당신도 할 수 있습니다 우리 자신의 진입 점을 설정하고, 또한 실행 가능한 라이브러리를 만들 수 있습니다 !


@vaxquis 맞습니다.하지만 이것은 main()C 프로그래밍 언어 에 함수를 바인딩하는 첫 번째 답변을 칭찬 / 수정하기 위해 작성한 부분적인 답변 입니다.
Ho1

@vaxquis "main () 함수는 C 프로그램에서 필수적이지 않다"는 설명이 부분적인 대답이라고 생각했습니다. 답을 완성하기 위해 단락을 추가했습니다. – Ho1 16 분 전
Ho1
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.