
이 문서는 일련의 문서 중 두 번째 부분입니다.
이 기사의 서문을 읽지 않았다면 먼저 읽으십시오.파트 1 소개
우리는 간단한 견고성 스마트 계약의 EVM 바이트코드를 분해하고 있습니다.
오늘은 "분할 정복" 전략을 사용하여 스마트 계약의 복잡한 코드를 분해해 보겠습니다. 소개 서문에서 말했듯이 이 디스어셈블된 코드는 실제로 매우 저수준이지만 원본 바이트 코드에 비해 상대적으로 읽기 쉽습니다.
서문에서 소개한 작업을 따르고 리믹스 컴파일러에 BasicToken 코드를 배포했는지 확인하십시오.
면책 조항: 이 기사에서 제공하는 모든 지침은 거래가 작동하는 방식에 대한 본인의 해석에 따르며 공식적인 이더리움 의견을 나타내지 않습니다.
지금,허락하다JUMP, JUMPI, JUMPDES, RETURN 및 STOP 작업에 중점을 두고 다른 모든 작업은 무시합니다. 이들 중 하나가 아닌 opcode를 찾을 때마다 우리는 그것을 무시하고 간섭하지 않고 다음 명령어로 건너뜁니다.
EVM이 코드를 실행할 때 하향식 순서이며 코드에 다른 진입점이 없으며 실행은 항상 위에서부터 시작됩니다. JUMP와 JUMPI는 코드 점프를 만들 수 있습니다. JUMP는 스택의 최상위 값을 가져와 해당 위치로 이동한 명령을 실행합니다. 그러나 대상 위치는 JUMPDEST opcode를 포함해야 합니다. 그렇지 않으면 실행이 실패합니다. 유일한 목적은 다음과 같습니다. JUMPDEST는 해당 위치를 유효한 점프 대상으로 표시합니다. JUMPI도 정확히 동일하지만 스택의 두 번째 위치에 "0"이 없어야 합니다. 그렇지 않으면 점프가 없습니다.그래서 이것은 조건부 점프이고, STOP은 스마트 컨트랙트를 완전히 중지하라는 지시이고, RETURN은 스마트 컨트랙트의 실행을 중지하되 데이터의 일부를 EVM 메모리에 반환하는 것이므로 매우 편리합니다.。
이제 이 모든 것을 염두에 두고 코드 설명을 시작하겠습니다. Remix의 디버거에서 "트랜잭션" 슬라이더를 왼쪽 끝까지 밉니다. Step Into 버튼(작은 아래쪽 화살표 모양)을 사용하고 지침을 따를 수 있습니다.
이전 명령은 무시할 수 있습니다. 11번째 명령으로 바로 이동하여 첫 번째 JUMPI를 찾았습니다. 점프하지 않으면 명령 12에서 15까지 계속되고 결국 REVERT에 들어가고 실행이 중지됩니다. 그러나 점프하면 해당 명령을 위치 16(명령 8에서 스택으로 푸시되는 16진수 0x0010)으로 건너뜁니다. 명령 16은 JUMPDEST입니다.
"트랜잭션" 슬라이더가 오른쪽 끝까지 올 때까지 opcode를 계속 단계별로 실행합니다. 방금 많은 일이 발생했지만 RETURN opcode는 위치 68에서만 찾을 수 있습니다(그리고 경우에 따라 STOP 명령 69의 opcode). 이것은 매우 이상합니다. 생각해 보면 이 스마트 계약의 제어 흐름은 항상 명령 15 또는 68에서 끝납니다. 방금 완료했고 가능한 다른 흐름이 없다고 판단했습니다. 그렇다면 나머지 지침은 무엇입니까? (지시문 패널을 스와이프하면 코드가 위치 566에서 끝나는 것을 볼 수 있습니다.)
방금 살펴본 지침 세트(0에서 69까지)는 계약의 "생성 코드"라고 하는 것입니다. 스마트 계약 코드 자체의 일부가 되지는 않지만 스마트 계약을 생성한 트랜잭션 동안 EVM에 의해 한 번만 실행됩니다. 곧 알게 되겠지만 이 코드는 생성된 계약의 초기 상태를 설정하고 런타임 코드의 복사본을 반환하는 역할을 합니다. 나머지 497개의 명령어(70~566),보시다시피 실행 흐름은 절대 도달하지 않으며 배포된 스마트 계약의 일부가 될 코드입니다.。
첫 번째 레벨 제목
섹션 만들기
이제 코드의 생성 부분을 자세히 살펴보겠습니다.
그림 1. BasicToken.sol의 생성 시 EVM 바이트코드 분해
이 기사에서 이해해야 할 가장 중요한 개념입니다. 생성 코드는 스마트 계약의 실제 코드인 런타임 코드의 복사본을 반환하는 트랜잭션에서 실행됩니다. 보시다시피 생성자는 런타임 코드가 아니라 생성 코드의 일부입니다. 스마트 계약의 생성자는 생성된 코드의 일부이며 일단 배포되면 스마트 계약의 코드에 나타나지 않습니다.
이 마법은 어떻게 일어날까요? 이것이 우리가 지금 단계별로 분석할 것입니다.
좋아요 이제 우리의 문제는 생성 시간 코드에 해당하는 70개의 명령을 이해하는 것으로 축소되었습니다.
하향식 접근 방식으로 돌아가서 이번에는 어떤 지침도 건너뛰는 대신 모든 지침을 알고 있습니다. 먼저 PUSH1 및 MSTORE opcode를 사용하는 명령어 0~2에 초점을 맞추겠습니다.
그림 2. 여유 메모리 포인터 EVM 바이트코드 구조
PUSH1은 단순히 스택 맨 위에 1바이트를 푸시하는 반면 MSTORE는 스택에서 마지막 두 항목을 가져와 그 중 하나를 메모리에 저장합니다.
mstore(0x40, 0x80)
| |
| What to store.
Where to store.
(in memory)
참고: 위의 코드 스니펫은 Yul-ish 코드입니다. 왼쪽에서 오른쪽으로 스택의 요소를 소비하는 방식에 주목하세요. 항상 스택 맨 위에 있는 요소를 먼저 소비합니다.
이것은 숫자 0x80(십진수 128)이 위치 0x40(십진수 64)에 저장되는 곳입니다.
우리가 지금 논의하는 문제는 그대로 두십시오. 이유가 있으면 나중에 설명하겠습니다.
이제 Remix의 디버거 탭에서 스택 및 메모리 패널을 열어 단계별로 이러한 지침을 시각화할 수 있습니다.
궁금하실 수 있습니다. 지침 1과 3은 어떻게 되었나요? PUSH는 2바이트 이상으로 구성된 유일한 EVM 명령어입니다. 따라서 PUSH 80은 두 개의 명령입니다. 그래서 우리는 수수께끼를 풀었습니다. 명령 1은 0x80이고 명령 3은 0x40입니다.
다음으로 5번부터 15번까지의 지시사항을 설명하겠습니다.
그림 3. 미납 수표 EVM 바이트코드 구조.
다시 한 번 CALLVALUE, DUP1, ISZERO, PUSH2 및 REVERT와 같은 새로운 opcode가 있습니다. CALLVALUE는 생성 트랜잭션에 관련된 wei의 수를 푸시하고, DUP1은 스택의 첫 번째 요소를 복사하고 ISZERO는 스택의 최고 값이 0인 경우 스택에 1을 푸시하고, PUSH2는 PUSH1과 같지만 스택에 2바이트를 푸시합니다. REVERT는 실행을 중지합니다.
그래서 여기서 무슨 일이 일어나고 있습니까? Solidity에서는 이 어셈블리를 다음과 같이 작성할 수 있습니다.
if(msg.value!= 0)revert();
이 코드는 원래 Solidity 소스의 일부가 아니지만 생성자를 유료로 선언하지 않았기 때문에 컴파일러에 의해 주입되었습니다. 최신 버전의 Solidity에서는 명시적으로 지불 가능하다고 선언되지 않은 함수는 이더를 받을 수 없습니다. 어셈블리 코드로 돌아가서 명령어 11의 JUMPI는 명령어 12에서 15까지 건너뛰거나 연결된 에테르가 없으면 16으로 점프합니다. 그렇지 않으면 REVERT는 두 개의 인수를 0으로 실행합니다(유용한 데이터가 반환되지 않음을 의미).
좋아요! 커피를 마시며 휴식을 취합시다.
(다음 부분은 조금 까다로울 것이므로 몇 분 쉬는 것이 가장 좋습니다. 다시 집중하기 전에 커피 한 잔을 준비하십시오. 지금까지 본 내용을 이해했는지 확인하십시오. 다음 부분이 조금 복잡하기 때문입니다.)
방금 완료한 것을 시각화하는 다른 방법을 원한다면 제가 만든 간단한 도구인 solmap을 사용해 보세요. Solidity 코드를 즉석에서 컴파일한 다음 EVM opcode를 클릭하여 관련 Solidity 코드를 강조 표시할 수 있습니다. 디스어셈블리는 리믹스와 조금 다르지만 비교를 통해 이해할 수 있어야 합니다.
커피 시간입니다!
계속 진행할 준비가 되셨습니까? 다음은 지침 16에서 37입니다. Remix의 디버거를 계속 사용하십시오. (기억하세요, 리믹스는 당신의 가장 친한 친구입니다 ^^).
그림 4. 스마트 계약 바이트코드 끝에 추가된 코드에서 생성자 매개변수를 검색하기 위한 EVM 바이트코드 구조
처음 네 개의 명령어(17~20)는 메모리의 위치 0x40에 있는 모든 것을 읽고 스택에 푸시합니다. 당신이 기억한다면 그것은 숫자 0x80이어야 합니다. 다음은 0x20(십진수 32)을 스택(명령어 21)에 푸시하고 해당 값을 복사하고(명령어 23), 0x0217(십진수 535)을 푸시하고(명령어 24) 마지막으로 네 번째 값(명령어 27)을 복사합니다. 0x80.
이와 같은 EVM 명령어를 볼 때 잠시 동안 무슨 일이 일어나고 있는지 보지 않아도 괜찮습니다. 걱정하지 마십시오. 때때로 머리에 떠오를 것입니다.
명령 28에서 CODECOPY가 실행됩니다.세 가지 인수를 취합니다: 복사된 코드가 저장되는 대상 메모리 위치, 복사할 명령어 번호, 복사할 코드의 바이트 수.따라서 이 경우 0x80은 코드에 위치한 바이트 위치(535, 32바이트 코드 길이의 대상 위치)에서 시작합니다.
전체 디스어셈블리 코드를 보면 566개의 명령어가 있습니다. 이 코드가 코드의 마지막 32바이트를 복사하려는 이유는 무엇입니까? 실제로 매개변수화된 생성자를 포함하는 계약을 배포할 때 매개변수는 코드 끝에 원시 16진수 데이터로 추가됩니다(설명 패널을 아래로 스크롤하여 확인). 이 경우 생성자는 uint256 매개변수를 사용하므로 이 코드가 수행하는 모든 작업은 코드 끝에 추가된 값에서 매개변수를 메모리로 복사하는 것입니다.
이 32개의 명령은 디스어셈블된 코드로 이해되지 않지만 원시 16진수로 표시됩니다. 물론 이것은 스마트 계약을 배포할 때 생성자에게 전달한 십진수 값 10000입니다!
Remix에서 이 부분을 단계별로 반복하여 방금 일어난 일을 이해할 수 있습니다. 최종 결과는 위치 0x00..002710이어야 합니다. 메모리의 숫자 0x80을 참조하십시오.
음, 다음 부분으로 넘어가기 전에 위스키와 함께 휴식을 취하는 것이 좋습니다.
위스키 시간!
위스키 한 잔을 권하는 이유는 여기에서 모든 것이 내리막이기 때문입니다.
다음 명령어 세트는 29에서 35까지이며 메모리 주소 0x40의 값 0x80을 값 0xa0으로 업데이트합니다. 보시다시피 값을 0x20(32) 바이트만큼 오프셋합니다.
이제 명령어 0에서 2까지 이해하기 시작할 수 있습니다. Solidity는 "null 포인터"라고 하는 것을 추적합니다. 이는 우리가 무언가를 저장할 수 있는 메모리의 장소이며, 아무도 덮어쓰지 않도록 보장합니다(우리가 실수하지 않는 한). 따라서 이전의 여유 메모리 위치에 숫자 10000을 저장했기 때문에 여유 메모리 포인터를 32바이트 앞으로 이동하여 업데이트합니다.
노련한 Solidity 개발자도 "자유 메모리 포인터" 또는 mload(0x40, 0x80) 코드를 보면 혼란스러워합니다. 기록".
Solidity의 모든 함수는 EVM 바이트코드로 컴파일될 때 이 포인터를 초기화합니다.
0x00에서 0x40 사이의 메모리에 무엇이 있는지 모를 수도 있습니다. 아니요. 솔리디티가 예약한 메모리 섹션은 해시 값을 계산하며 곧 보게 되겠지만 지도 및 기타 유형의 동적 데이터에 필요합니다.
이제 명령어 37에서 MLOAD는 메모리에서 위치 0x40을 읽고 기본적으로 메모리에서 스택으로 값 10000을 다운로드합니다. 그러면 다음 명령어 세트에서 사용할 수 있는 새로운 스택이 됩니다.
이것은 Solidity에 의해 생성된 EVM 바이트코드의 일반적인 패턴입니다. 함수 본문이 실행되기 전에 함수의 매개변수가 스택에 로드되어(가능한 경우) 다음 코드에서 사용할 수 있습니다. 이것이 바로 다음에 일어날 일입니다. 그 일이 일어났습니다.
38에서 55에 대한 설명을 계속하겠습니다.
그림 5. 생성자의 기본 EVM 코드.
이러한 지침은 생성자의 본체, 즉 Solidity 코드에 지나지 않습니다.
totalSupply_ = _initialSupply;
balances[msg.sender] = _initialSupply;
처음 4개의 명령은 매우 명백합니다(38에서 42). 먼저 0이 스택에 푸시되고 스택의 두 번째 항목이 복사됩니다(이것은 우리의 10000 숫자입니다). 그런 다음 숫자 0이 복사되어 스택에 푸시됩니다. 스토리지의 위치 슬롯 totalSupply_인 스택입니다. 이제 SSTORE는 이러한 값을 사용할 수 있으며 향후 사용을 위해 여전히 10000 미만으로 유지할 수 있습니다.
sstore(0x00, 0x2710)
| |
| What to store.
Where to store.
(in storage)
바라보다! 변수 totalSupply_에 숫자 10000을 저장합니다. 놀랍지 않나요??
Remix의 디버거 탭에서 이 값을 시각화해야 합니다. 상점 풀로드 패널에서 찾을 수 있습니다.
다음 지침 세트(43~54)는 약간 까다롭지만 기본적으로 잔액 맵에 10000의 msg.sender 키 저장을 처리합니다. 계속하기 전에 맵을 메모리에 유지하는 방법을 설명하는 Solidity 문서의 이 부분을 이해해야 합니다.
간단히 말해서 매핑된 값의 슬롯(이 경우 스마트 계약에서 선언된 두 번째 변수이므로 숫자 1)을 사용된 키(이 경우 msg.sender, opcode를 통해 CALLER를 가져옴)와 연결합니다. 그런 다음 SHA3 opcode로 다이제스트를 가져와 메모리의 대상으로 사용합니다. 결국 저장소는 단순한 사전 또는 해시 테이블일 뿐입니다.
명령어 43에서 45까지 계속해서 msg.sender 주소는 메모리(이번에는 위치 0x00)에 저장되고 명령어 46에서 50에서는 값 1(매핑된 슬롯)이 메모리 위치 0x20에 저장됩니다. 마지막으로 SHA3 opcode는 0x00 위치에서 0x40 위치까지 메모리에 있는 모든 항목의 Keccak256 해시를 계산합니다. 즉, 사용된 키와 매핑된 슬롯/위치의 연결입니다. 이것은 정확히 값 10000이 맵에 저장되는 위치입니다.
sstore(hash..., 0x2710)
| |
| What to store.
Where to store.
이 시점에서 생성자의 본문이 완전히 실행되었습니다.
이 모든 것이 처음에는 다소 부담스러울 수 있지만 Solidity 작업의 기본 부분입니다. 이해가 되지 않으면 스택 및 메모리 패널을 유지하면서 Remix의 디버거를 몇 번 사용하는 것이 좋습니다.
또한 다음과 같은 질문을 자유롭게 하십시오. 이 패턴은 솔리디티가 생성한 EVM 바이트코드에서 흔히 사용되는 패턴으로, 쉽게 인식하는 법을 금방 배우게 될 것입니다. 결국 메모리에서 맵의 특정 키에 대한 값을 보유할 위치를 계산합니다.
그림 6. 런타임 코드 복제 구조
지침 56~65에서 코드 복제를 다시 수행합니다. 이번에만 코드의 마지막 32바이트를 메모리에 복사하지 않고 위치 0x0046(십진수 70)에서 시작하여 0x01d1(십진수 465)바이트를 메모리 위치 0에 복사합니다. 복제할 엄청난 양의 코드입니다!
슬라이더를 다시 오른쪽 끝까지 밀면 위치 70이 빌드 타임 EVM 코드 바로 다음에 실행이 중지된다는 것을 알 수 있습니다. 런타임 바이트코드는 이 465바이트 내에 포함됩니다. 이것은 스마트 계약의 런타임 코드로 블록체인에 저장될 코드의 일부입니다., 코드는 누군가 또는 무언가가 스마트 계약과 상호 작용할 때마다 실행되는 코드입니다.(이 시리즈의 후반부에서 런타임 코드를 다룰 것입니다).
이것이 바로 명령어 66에서 69가 하는 일입니다. 메모리에 복사한 코드를 반환합니다.
그림 7. 런타임 코드는 EVM 바이트코드 구조를 반환합니다.
RETURN은 메모리에 복사된 코드를 잡고 EVM에 전달합니다. 이 생성 코드가 0x0 주소에 대한 트랜잭션 컨텍스트에서 실행되면 EVM은 코드를 실행하고 반환 값을 생성된 스마트 계약의 런타임 코드로 저장합니다.
이제 BasicToken 코드는 초기 상태 및 런타임 코드와 함께 사용할 준비가 된 스마트 계약 인스턴스를 생성 및 배포합니다. 한 걸음 물러서서 그림 2를 보면 보라색으로 강조 표시된 것을 제외하고 우리가 분석한 모든 EVM 바이트코드 구조가 일반적이라는 것을 알 수 있습니다. 즉, 바이트코드 생성 시 Solidity 컴파일러에 의해 생성됩니다. . 생성자는 생성자의 실제 본체인 보라색 부분에서만 생성자와 다릅니다. 바이트코드 끝에 매개변수를 포함하고 런타임 코드를 복사하여 반환하는 구조는 상용구 코드 및 일반 EVM opcode 구조로 생각할 수 있습니다. 이제 모든 생성자를 볼 수 있어야 하며 지침을 따르기 전에 생성자를 구성하는 구성 요소에 대한 일반적인 아이디어가 있어야 합니다.
이 시리즈의 다음 기사에서는 다양한 진입점에서 스마트 계약의 EVM 코드와 상호 작용하는 방법부터 시작하여 실제 런타임 코드를 다룰 것입니다. 이제 시리즈의 가장 어려운 부분을 소화했기 때문에 자신에게 충분한 칭찬을 해주세요. 또한 EVM 바이트코드를 읽고 디버그하고 공통 구조를 이해하며 가장 중요한 것은 빌드 타임과 런타임 EVM 바이트코드의 차이점을 아는 강력한 능력이 있어야 합니다. 이것이 Solidity에서 컨트랙트 생성자를 특별하게 만드는 것입니다.
Cheetah 블록체인 보안은 인공 지능, nlp 및 기타 기술과 결합된 Kingsoft Internet Security의 기술을 기반으로 블록체인 사용자에게 계약 감사 및 감정 분석과 같은 생태 보안 서비스를 제공합니다.
*이 기사는 Alejandro Santander가 매체에 처음 게시했으며 Cheetah Blockchain에서 번역 및 구성했습니다.*
Cheetah 블록체인 보안은 인공 지능, nlp 및 기타 기술과 결합된 Kingsoft Internet Security의 기술을 기반으로 블록체인 사용자에게 계약 감사 및 감정 분석과 같은 생태 보안 서비스를 제공합니다.
레이팅토큰 공식 홈페이지 https://www.ratingtoken.net/?from=z