LogFunctionNames 모듈 개발 - puts함수 호출코드 삽입

컴퓨터공부/엘엘비엠 2016.07.22 20:26
크리에이티브 커먼즈 라이선스
Creative Commons License

LogFunctionNames의 첫 버젼인 단순한 함수 이름 로깅 기능 구현을 마무리합니다

이전 글까지의 내용은

https://github.com/cd80/UtilizedLLVM/commit/4f397fa7b81eb0e3397041bf12ff4f4a48080709 

여기 정리되어 있습니다


이번 글에서는 puts함수가 항상 존재하는 상태에서 시작합니다. puts함수가 있다면 좋은것이고 없어도 동적으로 삽입해주니 이제부터 puts함수는 항상 존재합니다


이번에도 llc -march=cpp puts.ll에 의해 생성된 puts.cpp를 참고해 진행합니다

저번 글에서 만든 puts.cpp에서 main함수를 봅시다



 // Function: main (func_main)

 {


  BasicBlock* label_entry = BasicBlock::Create(mod->getContext(), "entry",func_main,0);


  // Block entry (label_entry)

  CallInst* int32_call = CallInst::Create(const_ptr_10, const_ptr_8, "call", label_entry);

  int32_call->setCallingConv(CallingConv::C);

  int32_call->setTailCall(false);

  AttributeSet int32_call_PAL;

  int32_call->setAttributes(int32_call_PAL);


  ReturnInst::Create(mod->getContext(), const_int32_9, label_entry);


 }


BasicBlock은 항상 하나 이상이 존재합니다. 이 이유는 Instruction의 parent가 BasicBlock이여야 하기 때문입니다


puts.c를 다시 봅시다

우리는 puts를 호출하는 코드밖에 작성하지 않았습니다

따라서

위의 cpp코드의 일부인

// Block entry (label_entry)

CallInst* int32_call = CallInst::Create(const_ptr_10, const_ptr_8, "call", label_entry);

int32_call->setCallingConv(CallingConv::C);

int32_call->setTailCall(false);

AttributeSet int32_call_PAL;

int32_call->setAttributes(int32_call_PAL);

위 코드는 모두 call puts를 만들기 위함임을 알 수 있습니다

저번 글에서 한것처럼 이걸 그대로 베껴봅시다


우선 우리는 저번 글에서 코드를 아래 스샷까지 작성했습니다

이 아래 

이렇게 puts함수를 호출하는 코드를 삽입하는 코드(편의상 이제부터는 puts함수를 호출하는 코드라고 부르겠습니다)

그럼 필요한 재료는 const_ptr_10, const_ptr_8, label_entry  이렇게 세가지가 됩니다


const_ptr_10부터 시작해봅시다

이걸 쓰기위해선 PointerTy_4가 필요하네요

또 FuncTy_5도 필요합니다

FuncTy_5는 PointerTy_3이 다시 필요한데 이건 다행히 붙어있네요

그럼 이 스샷 세개에 있는 코드를 합쳐봅시다



이제 const_ptr_8을 구해옵시다

이런게 지루할수도 있지만 하다보면 요리하는데 재료 구하러 산가서 나물캐오는 느낌이라 나름 재밌습니다

이번엔 gvar_array__str 이랑 const_ptr_8_indices가 필요합니다

gvar_array__str 부터 구해보죠

그런데 다행히 const_ptr_8_indices와 gvar_array__str이 모두 모여있네요



이 하나 떨어져있는거랑 위의 덩어리랑 합쳐봅시다




다했는데 빌드를 돌려보니 에러가 나네요

ArrayTy_0을 못봤습니다

ArrayTy_0도 넣어줍시다


이래도 빌드가 안됩니다

바로 이전 글들에서 언급했던, LLVM은 업데이트가 될때마다 API가 휙휙 바뀌는 문제 때문입니다

getGetElementPtr함수가 바뀌었는데요, 

LLVM 3.6.x까지는 getGetElementPtr이 우리가 사용한 형태인

static Constant *getGetElementPtr(Constant *C, ArrayRef<Constant *> IdxList,
                                    bool InBounds = false,
                                    Type *OnlyIfReducedTy = nullptr)

이렇게 생겼었습니다


3.7.x부터는 이게

static Constant *getGetElementPtr(Type *Ty, Constant *C,
                                    ArrayRef<Constant *> IdxList,
                                    bool InBounds = false,
                                    Type *OnlyIfReducedTy = nullptr)

맨 앞에 Type *Ty가 추가된 버젼으로 바뀝니다

우리가 테스트로 빌드할때 사용한 clang버젼도 3.8.0, llc버젼도 3.8.0인데 3.6버젼대의 API를 사용하는 코드로 변환된것입니다

그러려니합시다..


따라서 

여기서 사용한 getGetElementPtr에 맨 첫번째 인자로 Type을 넘겨줘야 하는데

여기서 이 Type에는 대체 어떻게 넣어야 하는지 알 수가 없으니 getGetElementPtr의 코드를 보면

이렇게 Ty가 0이면 그냥 알아서 설정해주는 부분이 있습니다



Type 인자에 0을 넣었습니다



그다음에 또 빌드를 해봅니다

label_entry를 까먹고 안넣었습니다

label_entry는 이 부분에서 사용하는데, 


http://llvm.org/docs/doxygen/html/classllvm_1_1CallInst.html

레퍼런스를 보면 우리는 

static CallInst * Create (Value *Func, ArrayRefValue * > Args, const Twine &NameStr, BasicBlock *InsertAtEnd)

이 함수를 사용했습니다

static CallInst * 

Create (Value *Func, ArrayRefValue * > Args, const Twine &NameStr, Instruction *InsertBefore=nullptr)

이거를 사용하도록 바꿀건데, InsertBefore는 기본으로 nullptr로 초기화 되므로,

이렇게 바꿔줍니다


이제 이 int32_call이라는 CallInst를 현재 함수의 첫번째 인스트럭션으로 지정해줄건데 쉽습니다

이렇게 함수의 첫번째 BasicBlock을 가져오고, Instruction List의 맨 처음에 int32_call을 푸쉬하면 됩니다


이제 중간 점검으로 빌드하고 돌려봅시다

지금까지 작성한 코드는 아래와 같습니다.

아직 끝난게 아니기 때문에 이전 글에서 말씀드린것과 마찬가지로 빌드 하면서 다른일을 못하신다면 굳이 지금 빌드하실 필요는 없습니다


#include "llvm/Pass.h"

#include "llvm/IR/Module.h"

#include "llvm/IR/Function.h"

#include "llvm/IR/BasicBlock.h"

#include "llvm/IR/Instructions.h"

#include "llvm/IR/InstrTypes.h"

#include "llvm/IR/Constants.h"

#include "llvm/IR/Type.h"

#include "llvm/ADT/Statistic.h"

#include "llvm/IR/GlobalValue.h"

#include "llvm/IR/LLVMContext.h"

#include "llvm/Transforms/Utils/Cloning.h"

#include "llvm/Transforms/Utils/BasicBlockUtils.h"

#include "llvm/CodeGen/ISDOpcodes.h"

#include "llvm/Support/raw_ostream.h"

#include "llvm/Support/Debug.h"

#include "llvm/Support/CommandLine.h"

#include "llvm/Transforms/IPO.h"

#include <list>


#include "llvm/Transforms/Utility/LogFunctionNames.h"

#include "llvm/IRReader/IRReader.h"

#include "llvm/Linker/Linker.h"

#include "llvm/Support/SourceMgr.h"

using namespace llvm;

namespace {

struct LogFunctionNames : public FunctionPass {

static char ID; // Pass identification

bool flag;

LogFunctionNames() : FunctionPass(ID) {}

LogFunctionNames(bool flag) : FunctionPass(ID) {this->flag = flag; LogFunctionNames();}


virtual bool runOnFunction(Function &F){

errs() << "Current function name: [" << F.getName() << "]\n";

errs() << "Current module name: [" << F.getParent()->getName() << "]\n";

Module *mod = F.getParent();

std::vector<Type*>FuncTy_6_args;

FunctionType* FuncTy_6 = FunctionType::get(/*Result=*/IntegerType::get(mod->getContext(), 32),

/*Params=*/FuncTy_6_args,

/*isVarArg=*/true);

Function* func_puts = mod->getFunction("puts");

if (!func_puts) {

func_puts = Function::Create(/*Type=*/FuncTy_6,

/*Linkage=*/GlobalValue::ExternalLinkage,

/*Name=*/"puts", mod); // (external, no body)

func_puts->setCallingConv(CallingConv::C);

}

AttributeSet func_puts_PAL;

{

SmallVector<AttributeSet, 4> Attrs;

AttributeSet PAS;

{

AttrBuilder B;

PAS = AttributeSet::get(mod->getContext(), ~0U, B);

}

Attrs.push_back(PAS);

func_puts_PAL = AttributeSet::get(mod->getContext(), Attrs);

}

func_puts->setAttributes(func_puts_PAL);


// prerequisites for const_ptr_10

PointerType* PointerTy_3 = PointerType::get(IntegerType::get(mod->getContext(), 8), 0);

std::vector<Type*>FuncTy_5_args;

FuncTy_5_args.push_back(PointerTy_3);

FunctionType* FuncTy_5 = FunctionType::get(/*Result=*/IntegerType::get(mod->getContext(), 32),

/*Params=*/FuncTy_5_args,

/*isVarArg=*/true);

PointerType* PointerTy_4 = PointerType::get(FuncTy_5, 0);

Constant* const_ptr_10 = ConstantExpr::getCast(Instruction::BitCast, func_puts, PointerTy_4);


// prerequisites for const_ptr_8

ArrayType* ArrayTy_0 = ArrayType::get(IntegerType::get(mod->getContext(), 8), 4);

GlobalVariable* gvar_array__str = new GlobalVariable(/*Module=*/*mod,

/*Type=*/ArrayTy_0,

/*isConstant=*/true,

/*Linkage=*/GlobalValue::PrivateLinkage,

/*Initializer=*/0, // has initializer, specified below

/*Name=*/".str");

gvar_array__str->setAlignment(1);


Constant *const_array_7 = ConstantDataArray::getString(mod->getContext(), "hi~", true);

std::vector<Constant*> const_ptr_8_indices;

ConstantInt* const_int32_9 = ConstantInt::get(mod->getContext(), APInt(32, StringRef("0"), 10));

const_ptr_8_indices.push_back(const_int32_9);

const_ptr_8_indices.push_back(const_int32_9);

Constant* const_ptr_8 = ConstantExpr::getGetElementPtr(0, gvar_array__str, const_ptr_8_indices);

gvar_array__str->setInitializer(const_array_7);


CallInst* int32_call = CallInst::Create(const_ptr_10, const_ptr_8, "call");

int32_call->setCallingConv(CallingConv::C);

int32_call->setTailCall(false);

AttributeSet int32_call_PAL;

int32_call->setAttributes(int32_call_PAL);


F.getEntryBlock().getInstList().push_front(int32_call);

return false;

}

};

}


char LogFunctionNames::ID = 0;

static RegisterPass<LogFunctionNames> X("logger", "inserting logging routine for functions");


Pass *llvm::createLogger() {

return new LogFunctionNames();

}


Pass *llvm::createLogger(bool flag) {

return new LogFunctionNames(flag);

}




빌드하고 돌려보세요

또잉~~ 함수이름이 출력된게 아니라 hi~ 가 출력이 됐네요

그 이유가 궁금하신분은 puts.c를 읽어보세요

puts("hi~"); 라고 적혀있을겁니다

우리는 그걸 그대로 베껴왔으니 puts("hi~"); 가 함수마다 삽입된겁니다

그래도 puts함수 호출에는 성공했으니 거의다 왔습니다 조금만 더 해봅시다


hi~는 어딨는걸까요?

아까 gvar_array__str 를 설정한 부분입니다

맨위에 hi~가 있죠

메세지를 어떻게 하는지는 자유지만 저는 아래처럼 했습니다

free까지 해주면 완벽하겠지만 쉽게쉽게 합시다

코드의 가독성을 위해 메모리 관리를 포기하는 미친짓을 해도 괜찮습니다. 제품을 내려는게 아니라 공부하려는 거니까요 하하

한가지 더 덧붙일건, 지금 현재 코딩 스타일은 굉장히 쓰레기라는 겁니다. 최대한 설명하기에 편하도록 예제코드에서 그대로 복붙하는 방식을 쓰고 있는데, 실제로 코드를 릴리즈 할땐 이쁘게 작성하세요


이대로 빌드하면 빌드는 잘되지만 컴파일할때 에러가 생깁니다

이번 에러는 블로그에선 다루지 않겠지만, 코드를 보면 바로 이해하실 수 있습니다

스스로 해결하는 시간이 굉장히 중요하기 때문에 스스로 해결해보시고, 해결을 완료하거나 도저히 모르겠다 싶을 땐

https://github.com/cd80/UtilizedLLVM/commit/01241a782506a485220b48bb68654c843d8d4a3e

이 커밋을 참고해보세요. 글에서 설명한것과 코드가 어떻게 다른가 보면 됩니다




완성했습니다~_~_~

우리가 개발한건 완전히 초기버젼으로, 앞으로 이 기능을 좀더 간지나게 개선하긴 하겠지만

우선 지금은 간지나게 개선하는 것보단 새로운 기능을 또 구현해보는게 더 재밌으니 개선은 미루고 다음 글에서는 새로운 기능을 시작해보겠습니다

신고

설정

트랙백

댓글

LogFunctionNames 모듈 개발 - puts함수 동적 삽입

컴퓨터공부/엘엘비엠 2016.07.22 20:26
크리에이티브 커먼즈 라이선스
Creative Commons License

우리는 저번 글에서 컴파일 할 때 함수 이름들을 로깅하는것을 테스트 해봤습니다

이번 글과 다음 글에서는 함수 이름 로깅 기능을 컴파일러에서 수행하는 것이 아닌, 해당 기능을 수행하는 코드를 컴파일되는 프로그램에 삽입합니다

하다가 중간결과를 확인하고 싶으실 때는 직접 빌드해서 돌려보시면 됩니다. 언제 빌드해서 확인해볼만 한지는 글 중간 중간 말씀드리겠습니다



위 사진은 우리가 가장 많이 사용할 각각 요소에 대한 포함관계를 설명합니다


좀더 쉬운 이해를 위해 아래 아이다 사진을 참고해주세요

우선 Module은 아이다에서 이해하기 쉽지는 않습니다. LLVM 에서 모듈이란 각 파일마다 구성됩니다.

예를 들어 이전 글에서 ./clang -o test test.c로 컴파일한 코드에 F.getParent()->getName() 을 로깅해보면 test.c가 출력됩니다


그 다음 Function은 이름 그대로 각각 함수마다 구성됩니다.


이 설명을 시작한 이유는, 다른건 모두 직관적으로 알 수 있지만 Basic Block에 대한 개념을 모르시는 분이 계실까봐입니다

BasicBlock은 일반적으로 분기와 분기 사이의 코드를 뜻합니다

즉 항상 선형으로 동작되는 코드들의 모음을 BasicBlock이라고 하고, IDA에서 그래프뷰로 볼때 각각 직사각형은 모두 Basic Block들입니다



이전 글에서 세팅한 뼈대 코드와 함수 이름을 로깅하는 코드에

errs() << "Current module name: [" << F.getParent()->getName() << "]\n";

위 코드를 추가하고 테스트 해보세요 


getParent()함수는 GlobalValue 클래스에서 정의합니다


Parent 멤버 선언

http://llvm.org/docs/doxygen/html/GlobalValue_8h_source.html#l00115

getParent 함수 구현체

http://llvm.org/docs/doxygen/html/GlobalValue_8h_source.html#l00361


즉 Parent는 Module * 형으로 선언돼있고 getParent()는 Parent를 리턴하기 때문에, getParent()의 리턴 형은 Module *입니다

그러면 Instruction이나 BasicBlock 클래스에서도 getParent가 Module을 리턴하느냐 하면 아닙니다. Instruction과 BasicBlock 클래스에서는 함수들이 모두 오버라이딩 돼있습니다

각각 레퍼런스를 확인해보면 맨위의 다이어그램에 맞게 parent를 리턴하는 것을 볼 수 있습니다



이제 첫 주제인 puts함수 삽입을 어떻게 하는가를 알아보겠습니다

그전에 우리는 LLVM IR파일에 좀더 익숙해져야 합니다


단순한 코드를 작성합니다

우리가 처음 쉘코드를 공부할 때 gcc -S write.c 와 같이 하면 write.S가 생성이 되고, 여긴 어셈블리어 코드가 들어가 있었습니다

llvm 에서도 똑같이 할 수 있는데, -emit-llvm이라는 옵션을 추가로 주면 어셈블리 파일이 아닌 LLVM IR파일을 생성해줍니다



IR파일을 읽으면 아래와 비슷하게 나옵니다

; ModuleID = 'puts.c'

target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"

target triple = "x86_64-unknown-linux-gnu"


@.str = private unnamed_addr constant [4 x i8] c"hi~\00", align 1


; Function Attrs: nounwind uwtable

define i32 @main() #0 {

entry:

  %call = call i32 (i8*, ...) bitcast (i32 (...)* @puts to i32 (i8*, ...)*)(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str, i32 0, i32 0))

  ret i32 0

}


declare i32 @puts(...) #1


attributes #0 = { nounwind uwtable "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+sse,+sse2" "unsafe-fp-math"="false" "use-soft-float"="false" }

attributes #1 = { "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+sse,+sse2" "unsafe-fp-math"="false" "use-soft-float"="false" }


!llvm.ident = !{!0}


!0 = !{!"clang version 3.8.0 (trunk 247122)"}

지금 제 시스템에 깔려있는 LLVM 버젼이 3.8.0이기 때문에 다른 버젼으로 컴파일하신다면 결과가 약간 다르게 나올 순 있지만 지금은 상관없습니다

IR 결과에서 함수만 추려보면

define i32 @main() #0 {

entry:

  %call = call i32 (i8*, ...) bitcast (i32 (...)* @puts to i32 (i8*, ...)*)(i8* getelementptr inbounds ([4 x i8], [4 x i8]* @.str, i32 0, i32 0))

  ret i32 0

}


declare i32 @puts(...) #1

이렇게 main과 puts가 존재하는것을 알 수 있습니다

두 함수의 차이점은 하나는 define된 함수고 하나는 declare된 함수라는 것입니다

정확히 알진 못하더라도 느낌상 define된 함수는 현재 코드에서 함수가 정의된단걸 알 수 있고

declare된 함수는 다른 소스파일이나 라이브러리에 정의돼있는 함수란걸 알 수 있습니다

puts함수도 현재 코드에서 정의되는 함수가 아닌 라이브러리에서 정의된 함수죠


그러면 declare 될 함수를 어떻게 LLVM Pass에서 삽입할 수 있을까요?

이런 애매한걸 해결하기 가장 쉬운 방법은 IR파일을 LLVM API를 이용한 c++코드로 바꾸는 것입니다

저도 이 기능을 발견했을 때 매우 행복했는데, 어떤 기능인지 보여드리겠습니다


생성된 puts.cpp를 읽어서 puts 를 검색해보세요

막연하게만 느껴지던 puts함수 삽입을 이렇게 친절한 예제로 볼 수 있습니다

한줄한줄 완벽한 이해는 안되더라도 이렇게하면  위의 IR코드 처럼

declare i32 @puts(...) #1

이 한 줄을 IR파일에 자동으로 추가할 수 있겠죠


코드에 대한 약간의 설명을 드리자면

우선 mod는 현재 모듈을 가르키는 변수입니다. 우리의 경우엔 F.getParent()를 변수로 저장해서 사용하면 되겠죠

FuncTy_6은 잠시 후에 보도록 하고

GlobalValue::ExternalLinkage 는 http://llvm.org/docs/doxygen/html/classllvm_1_1GlobalValue.html#aedfa75f0c85c4aa85b257f066fbea57c 이 링크를 참고해주세요. 

그다음에 Attributes에 관련되어 보이는 코드들은 

attributes #1 = { "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+sse,+sse2" "unsafe-fp-math"="false" "use-soft-float"="false" }

이 부분을 만들어주는 코드입니다

그러려니 합시다..


위에서 스킵한 FuncTy_6은 여기있습니다

뭔진 모르겠지만 puts함수의 타입은 이런가봅니다

왜 isVarArg 가 true인진 모르겠지만 그러려니합시다..



        virtual bool runOnFunction(Function &F){

            errs() << "Current function name: [" << F.getName() << "]\n";

            errs() << "Current module name: [" << F.getParent()->getName() << "]\n";

            Module *mod = F.getParent();

            std::vector<Type*>FuncTy_6_args;

            FunctionType* FuncTy_6 = FunctionType::get(/*Result=*/IntegerType::get(mod->getContext(), 32),

                                                        /*Params=*/FuncTy_6_args,

                                                        /*isVarArg=*/true);

            Function* func_puts = mod->getFunction("puts");

            if (!func_puts) {

                func_puts = Function::Create(/*Type=*/FuncTy_6,

                                            /*Linkage=*/GlobalValue::ExternalLinkage,

                                            /*Name=*/"puts", mod); // (external, no body)

                func_puts->setCallingConv(CallingConv::C);

            }

            AttributeSet func_puts_PAL;

            {

                SmallVector<AttributeSet, 4> Attrs;

                AttributeSet PAS;

                {

                    AttrBuilder B;

                    PAS = AttributeSet::get(mod->getContext(), ~0U, B);

                }

                Attrs.push_back(PAS);

                func_puts_PAL = AttributeSet::get(mod->getContext(), Attrs);

            }

            func_puts->setAttributes(func_puts_PAL);

            return false;

        }

FuncTy_6과 puts를 삽입하는 코드를 그대로 옮겼습니다

임의로 추가한 부분은 4번째줄의 Module *mod = F.getParent(); 입니다


자 그럼 이제 테스트 해봅시다

빌드를 이미 완전히 해보셨다면 금방 빌드가 완료됩니다

컴퓨터 사양에따라 중간 중간 빌드하는게 무리다 싶으시면 제가 보여드리는 결과만 보고 넘어가셔도 괜찮습니다. 그래도 직접 확인해보는게 훨씬 와닿겠죠



⚡ root@vultr  ~/build/bin  clang -S -emit-llvm test.c -w

 ⚡ root@vultr  ~/build/bin  cat test.ll

; ModuleID = 'test.c'

target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"

target triple = "x86_64-unknown-linux-gnu"


@.str = private unnamed_addr constant [8 x i8] c"5+3=%d\0A\00", align 1


; Function Attrs: nounwind uwtable

define i32 @add(i32 %x, i32 %y) #0 {

entry:

  %x.addr = alloca i32, align 4

  %y.addr = alloca i32, align 4

  store i32 %x, i32* %x.addr, align 4

  store i32 %y, i32* %y.addr, align 4

  %0 = load i32, i32* %x.addr, align 4

  %1 = load i32, i32* %y.addr, align 4

  %add = add nsw i32 %0, %1

  ret i32 %add

}


; Function Attrs: nounwind uwtable

define i32 @main() #0 {

entry:

  %call = call i32 @add(i32 5, i32 3)

  %call1 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([8 x i8], [8 x i8]* @.str, i32 0, i32 0), i32 %call)

  ret i32 0

}


declare i32 @printf(i8*, ...) #1


attributes #0 = { nounwind uwtable "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+sse,+sse2" "unsafe-fp-math"="false" "use-soft-float"="false" }

attributes #1 = { "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+sse,+sse2" "unsafe-fp-math"="false" "use-soft-float"="false" }


!llvm.ident = !{!0}


!0 = !{!"clang version 3.8.0 (trunk 247122)"}

 ⚡ root@vultr  ~/build/bin 

지금 이건 시스템에 설치돼있던 LLVM, 즉 puts함수를 자동으로 삽입하는 코드가 없는 LLVM입니다


 ✘ ⚡ root@vultr  ~/build/bin  ./clang -S -emit-llvm test.c -w

Current function name: [add]

Current module name: [test.c]

Current function name: [main]

Current module name: [test.c]

 ⚡ root@vultr  ~/build/bin  cat test.ll

; ModuleID = 'test.c'

source_filename = "test.c"

target datalayout = "e-m:e-i64:64-f80:128-n8:16:32:64-S128"

target triple = "x86_64-unknown-linux-gnu"


@.str = private unnamed_addr constant [8 x i8] c"5+3=%d\0A\00", align 1


; Function Attrs: nounwind uwtable

define i32 @add(i32 %x, i32 %y) #0 {

entry:

  %x.addr = alloca i32, align 4

  %y.addr = alloca i32, align 4

  store i32 %x, i32* %x.addr, align 4

  store i32 %y, i32* %y.addr, align 4

  %0 = load i32, i32* %x.addr, align 4

  %1 = load i32, i32* %y.addr, align 4

  %add = add nsw i32 %0, %1

  ret i32 %add

}


; Function Attrs: nounwind uwtable

define i32 @main() #0 {

entry:

  %call = call i32 @add(i32 5, i32 3)

  %call1 = call i32 (i8*, ...) @printf(i8* getelementptr inbounds ([8 x i8], [8 x i8]* @.str, i32 0, i32 0), i32 %call)

  ret i32 0

}


declare i32 @printf(i8*, ...) #1


declare i32 @puts(...)


attributes #0 = { nounwind uwtable "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-jump-tables"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }

attributes #1 = { "disable-tail-calls"="false" "less-precise-fpmad"="false" "no-frame-pointer-elim"="true" "no-frame-pointer-elim-non-leaf" "no-infs-fp-math"="false" "no-nans-fp-math"="false" "no-signed-zeros-fp-math"="false" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+fxsr,+mmx,+sse,+sse2,+x87" "unsafe-fp-math"="false" "use-soft-float"="false" }


!llvm.ident = !{!0}


!0 = !{!"clang version 3.9.0 (trunk 275527) (https://github.com/cd80/UtilizedLLVM.git 2587b3df7a08ff644b1b31139619798829d7f95e)"}

 ⚡ root@vultr  ~/build/bin 

puts가 생겼습니다!


지금 우리가 어떤 지식을 기반으로 했다기 보다 일종의 예제코드를 보고 복사붙여넣기를 한것이기 떄문에 아직 우리가 LLVM에 대해서 안다! 라고는 할 수 없는 단계지만

이 방식으로도 충분히 우리가 원하는 어떤 기능이든지 다 구현할 수 있습니다



쓰는게 힘에부쳐서 한 글에 많은 내용을 담지는 못합니다

다음 글에서는 동적으로 삽입된 puts함수에 우리가 원하는 문자열을 인자로 전달하고 호출하는 코드를 작성한 뒤 이를 각 함수의 맨 처음 인스트럭션으로 삽입하는 과정을 다루겠습니다

직접 구글과 레퍼런스를 참고하면서 구현해보시고 저는 어떻게 구현하는지 보러오시는것도 좋은 공부가 될것 같습니다

신고

설정

트랙백

댓글

LogFunctionNames 모듈 개발 - 뼈대 작성과 컴파일타임에서의 함수 이름 로깅

컴퓨터공부/엘엘비엠 2016.07.15 22:58
크리에이티브 커먼즈 라이선스
Creative Commons License

이 강의글의 컨셉은 LLVM을 깊게 이해한 개발이 아닌 일단 쓰는 법을 익혀보자 이기 때문에 아주 자세한 내용은 다루지 않습니다


ModulePass는 모듈을 변조하거나 최적화할때 사용하고, FunctionPass는 함수, BasicBlockPass는 각 basic block별로 적용합니다

그 중에서 우리는 각 함수의 맨 첫부분에 함수의 이름을 로깅하는 코드를 작성할 것이기 때문에 FunctoinPass를 사용합니다. 해당 개발은 다음 글에서 진행합니다


아래에서 나오는 UtilizedLLVM은 http://clang.llvm.org/get_started.html 을 그대로 따라한 LLVM 3.9.0 버젼입니다. LLVM이 업데이트되어 저 링크를 보고 따라했는데 3.9.0 이상버젼이 나온다면 이전 글에서 3.6.2를 받았을 때와 마찬가지로 branch를 지정해서 받으시면 됩니다


우선 lib/Transforms/ 로 이동합니다

Hello는 dummy module로, LLVM 모듈 개발시 참고할 만 하나 이것보다는 좀더 쉬운 예제로 시작하겠습니다


우선 우리 코드들이 들어갈 Utility 디렉토리를 생성하고 CMakeLists.txt와 LLVMBuild.txt를 수정합니다



Utility 디렉토리에서도 CMakeLists.txt를 작성해야합니다

LLVM Obfuscator 의 CMakeLists.txt를 참고하겠습니다

https://github.com/obfuscator-llvm/obfuscator/blob/llvm-3.6.1/lib/Transforms/Obfuscation/CMakeLists.txt

LLVM Pass들은 모두 LLVM 프리픽스를 가진 라이브러리 파일로 빌드됩니다

우리 모듈은 LLVMUtility로 정했고 처음 개발해볼 모듈인, 함수가 호출될때 해당 함수의 이름을 로깅하는 모듈의 파일명을 LogFunctionNames.cpp 로 정했습니다


다음으로 LLVMBuild.txt를 작성합니다

마찬가지로 LLVM Obfuscator에서 LLVMBuild.txt를 가져와서 Obfuscation을 Utility로 변경합니다

https://github.com/obfuscator-llvm/obfuscator/blob/llvm-3.6.1/lib/Transforms/Obfuscation/LLVMBuild.txt


이제 LogFunctionNames.cpp를 작성하면 됩니다

이번에도 뼈대를 LLVM Obfuscator에서 가져오겠습니다

https://github.com/obfuscator-llvm/obfuscator/blob/llvm-3.6.1/lib/Transforms/Obfuscation/BogusControlFlow.cpp

이 파일을 받아 LogFunctionNames.cpp로 파일명을 변경하고 주석과 include, 플래그를 모두 지웁니다


최종적으로 이런 형태가 되면 됩니다

이제 몇가지 수정을 하겠습니다

BogusControlFlow를 LogFunctionNames로 바꾸고

createBogus 를 createLogger로

static RegisterPass<BogusControlFlow> X("boguscf", "inserting bogus control flow"); 를 static RegisterPass<LogFunctionNames> X("logger", "inserting logging routine for functions"); 로 바꿉니다



수정할것이 약간 많아 설명을 건너뛰고 마지막으로 어떤 형태가 돼야 하는지 보여드리겠습니다

#include "llvm/Pass.h"

#include "llvm/IR/Module.h"

#include "llvm/IR/Function.h"

#include "llvm/IR/BasicBlock.h"

#include "llvm/IR/Instructions.h"

#include "llvm/IR/InstrTypes.h"

#include "llvm/IR/Constants.h"

#include "llvm/IR/Type.h"

#include "llvm/ADT/Statistic.h"

#include "llvm/IR/GlobalValue.h"

#include "llvm/IR/LLVMContext.h"

#include "llvm/Transforms/Utils/Cloning.h"

#include "llvm/Transforms/Utils/BasicBlockUtils.h"

#include "llvm/CodeGen/ISDOpcodes.h"

#include "llvm/Support/raw_ostream.h"

#include "llvm/Support/Debug.h"

#include "llvm/Support/CommandLine.h"

#include "llvm/Transforms/IPO.h"

#include <list>


#include "llvm/Transforms/Utility/LogFunctionNames.h"

using namespace llvm;

namespace {

struct LogFunctionNames : public FunctionPass {

static char ID; // Pass identification

bool flag;

LogFunctionNames() : FunctionPass(ID) {}

LogFunctionNames(bool flag) : FunctionPass(ID) {this->flag = flag; LogFunctionNames();}


virtual bool runOnFunction(Function &F){

errs() << "Current function name: [" << F.getName() << "]\n";

return false;

}

};

}


char LogFunctionNames::ID = 0;

static RegisterPass<LogFunctionNames> X("logger", "inserting logging routine for functions");


Pass *llvm::createLogger() {

return new LogFunctionNames();

}


Pass *llvm::createLogger(bool flag) {

return new LogFunctionNames(flag);

}

이렇게 되면 뼈대는 우선 작성을 완료했습니다



여기서 include한 파일중 하나인 "llvm/Transforms/Utility/LogFunctionNames.h"을 작성합니다

//===- LogFunctionNames.h - LogFunctionNames Utility pass-------------------------===//

//

//                     The LLVM Compiler Infrastructure

//

// This file is distributed under the University of Illinois Open Source

// License. See LICENSE.TXT for details.

//

//===--------------------------------------------------------------------------------===//

//

// This file contains includes and defines for the LogFunctionNames pass

//

//===--------------------------------------------------------------------------------===//


#ifndef _LOGFUNCTIONNAMES_H_

#define _LOGFUNCTIONNAMES_H_



// LLVM include

#include "llvm/Pass.h"

#include "llvm/IR/Module.h"

#include "llvm/IR/Function.h"

#include "llvm/IR/BasicBlock.h"

#include "llvm/IR/Instructions.h"

#include "llvm/IR/InstrTypes.h"

#include "llvm/IR/Constants.h"

#include "llvm/IR/Type.h"

#include "llvm/ADT/Statistic.h"

#include "llvm/IR/GlobalValue.h"

#include "llvm/IR/LLVMContext.h"

#include "llvm/Transforms/Utils/Cloning.h"

#include "llvm/Transforms/Utils/BasicBlockUtils.h"

#include "llvm/CodeGen/ISDOpcodes.h"

#include "llvm/Support/raw_ostream.h"

#include "llvm/Support/Debug.h"

#include "llvm/Support/CommandLine.h"

#include "llvm/Transforms/IPO.h"

#include <list>


using namespace std;

using namespace llvm;


// Namespace

namespace llvm {

Pass *createLogger ();

Pass *createLogger (bool flag);

}

#endif 


여기까지만 해도 빌드는 되지만 아직 createLogger()를 PassManager에 등록하지 않았습니다


LLVMObfuscator에서는 populateModulePassManager에서 모든 Pass들을 등록했지만 우리는 용도에 맞게 populateFunctionPassManager에서 createLogger를 FunctionPassManager에 추가하겠습니다

lib/Transforms/IPO/PassManagerBuilder.cpp 를 수정합니다

우선 #include "llvm/Transforms/Utility/LogFunctionNames.h" 을 인클루드 하고


void PassManagerBuilder::populateFunctionPassManager(

    legacy::FunctionPassManager &FPM) {

  addExtensionsToPM(EP_EarlyAsPossible, FPM);

  FPM.add(createLogger());

  // Add LibraryInfo if we have some.

  if (LibraryInfo)

    FPM.add(new TargetLibraryInfoWrapperPass(*LibraryInfo));

  ...생략...

}

위와같이 추가해줍니다


이상태에서 빌드하면 이전 글에서 언급했던 오류중에 하나인

/root/UtilizedLLVM/lib/Transforms/IPO/PassManagerBuilder.cpp:214: undefined reference to `llvm::createLogger()'

이런 에러가 발생합니다

해결 하는 방법은 이전 글에 있으니 기억이 안나신다면 다시 보고 오셔서 해결해보시길 바랍니다

지금부터는 이 오류를 해결한 상태에서 진행하기 때문에 꼭 지금 해결하셔야 합니다






http://clang.llvm.org/get_started.html

빌드법은 위 링크 7번 참고하세요


빌드는 꽤 오래걸립니다. 그래도 앞으로는 우리 모듈이 수정되면 우리 모듈만 새로 빌드해서 링크하기 때문에 처음에만 조금 기다리면 됩니다

소스코드는 어떤걸 사용해도 무방하나 제가 자주 테스트용으로 사용하는 코드는 아래와 같습니다. 스샷에서 사용한 코드와 같은 코드입니다

int add(int x, int y){

return x+y;

}

int main(){

printf("5+3=%d\n", add(5,3));

}




문제 하나.

undefined reference to ~~에러

이전 글 참고해주세요


문제 둘.

make 시 ld가 segmentation fault로 죽음

저는 링킹중 메모리 용량이 부족해 이런 문제가 발생했습니다

아래 링크를 참고해 swap file을 설정하시면 됩니다

https://www.digitalocean.com/community/tutorials/how-to-add-swap-on-ubuntu-14-04




첫 유틸리티 모듈은 글의 초반부에서 얘기했던 것 처럼, 프로그램의 각 함수마다 맨 처음에 함수의 이름을 출력하는 기능을 할것이기 때문에 이번 글에서는 함수의 이름을 동적으로 어떻게 가져오는지를 알아보고, 모듈 개발시 자잘한 부분인 구조적 뼈대를 갖추는 법에 대해 이전 글에 이어서 자세히 설명했습니다

다음 글에서는 puts함수를 타겟 모듈에 동적으로 삽입하는 법에 대해 다루겠습니다



LLVM API를 이용해 개발하다 보면 어느 개발에서나 그렇 듯 레퍼런스를 많이 읽어봐야 합니다

다음 글에서 다룰 함수를 동적 삽입하는 경우에도 아주 간단한 함수 하나로 끝낼 수 있지만 레퍼런스를 잘 보지 않아 300줄가량의 코드를 작성했던 적이 있습니다

오늘 코드에서는 Function 클래스의 getName 메소드만을 사용했는데, 

http://llvm.org/docs/doxygen/html/classllvm_1_1Function.html

위의 function 클래스의 레퍼런스를 보면 getName 메소드가 존재하지 않습니다

ctrl+f를 누르고 검색해보면 Value 클래스에 정의되어있는 것을 알 수 있고, 레퍼런스 맨 위의 inheritance diagram을 보면 Function이 Value를 최상위클래스로써 상속받는 다는 것을 알 수 있습니다

Value 클래스는 앞으로도 항상 보게 될 클래스이므로 이번 글에서 다룬 Function 클래스와 함께 레퍼런스를 훑어보시길 추천드립니다

신고

설정

트랙백

댓글

LLVM 입문 - LLVM Obfuscator의 구조

컴퓨터공부/엘엘비엠 2016.07.15 21:04
크리에이티브 커먼즈 라이선스
Creative Commons License

이번 글에서는 LLVM 3.6.1기반의 LLVM Obfuscator와, LLVM 3.6.1을 디핑해 LLVM을 커스터마이즈 하기 위해 어떻게 하면 되는 가를 공부합니다


먼저 LLVM Obfuscator를 클론합니다


git clone -b llvm-3.6.1 https://github.com/obfuscator-llvm/obfuscator.git



이제 LLVM 3.6.1을 받아야하는데, http://clang.llvm.org/get_started.html 을 그대로 따라하면 최신버젼으로 받아지기 때문에 release_36 브랜치를 직접 지정해 받아야합니다.

약간 슬픈일은 release_36브랜치의 버젼이 3.6.1이 아닌 3.6.2입니다

그래서 약간 차이가 더 생기긴 하지만 그래도 3.9.0이랑 비교하는것보단 적게 차이가 존재합니다


예를들어

svn co http://llvm.org/svn/llvm-project/llvm/trunk llvm

이 명령문을

svn co http://llvm.org/svn/llvm-project/llvm/branches/release_36 llvm

으로 바꿔 실행하면됩니다.

그 외에는 모두 동일하게 하면 되는데 libcxx는 건너뛰어주세요


모두 완료하시면 위와 같이 llvm 디렉토리와 obfuscator디렉토리가 존재합니다


diff 결과가 263줄이나 나오지만 이건 LLVM 모듈 개발하면서 겪는 상황보다는 아주 쾌적합니다

그리고 다행히 이중에서 약 10줄정도만 보면 됩니다


`

이 밑으로도 약 200개 가량의 결과가 있지만 총 diff결과(스샷에 있는것 외에도)에서 필요한것만 추려보자면

Only in obfuscator/include/llvm: CryptoUtils.h

Only in obfuscator/include/llvm/Transforms: Obfuscation

Files llvm/lib/Transforms/CMakeLists.txt and obfuscator/lib/Transforms/CMakeLists.txt differ

Files llvm/lib/Transforms/IPO/LLVMBuild.txt and obfuscator/lib/Transforms/IPO/LLVMBuild.txt differ

Files llvm/lib/Transforms/IPO/PassManagerBuilder.cpp and obfuscator/lib/Transforms/IPO/PassManagerBuilder.cpp differ

Files llvm/lib/Transforms/LLVMBuild.txt and obfuscator/lib/Transforms/LLVMBuild.txt differ

Files llvm/lib/Transforms/Makefile and obfuscator/lib/Transforms/Makefile differ

Only in obfuscator/lib/Transforms: Obfuscation

위 7개 결과의 차이점을 분석해보겠습니다

우선 맨 윗 두줄은 include 경로에 있는 차이기 때문에 굳이 보지 않겠습니다


 ✘ ⚡ root@vultr  ~/diffLLVM  diff  llvm/lib/Transforms/CMakeLists.txt  obfuscator/lib/Transforms/CMakeLists.txt

8a9

> add_subdirectory(Obfuscation)

 ✘ ⚡ root@vultr  ~/diffLLVM 

맨처음으로 lib/Transforms/CMakeLists.txt 의 차이가 존재합니다


 ⚡ root@vultr  ~/diffLLVM/obfuscator/lib/Transforms   llvm-3.6.1  cat CMakeLists.txt

add_subdirectory(Utils)

add_subdirectory(Instrumentation)

add_subdirectory(InstCombine)

add_subdirectory(Scalar)

add_subdirectory(IPO)

add_subdirectory(Vectorize)

add_subdirectory(Hello)

add_subdirectory(ObjCARC)

add_subdirectory(Obfuscation)

 ⚡ root@vultr  ~/diffLLVM/obfuscator/lib/Transforms   llvm-3.6.1  ls

CMakeLists.txt  Hello  IPO  InstCombine  Instrumentation  LLVMBuild.txt  Makefile  Obfuscation  ObjCARC  Scalar  Utils  Vectorize

 ⚡ root@vultr  ~/diffLLVM/obfuscator/lib/Transforms   llvm-3.6.1 

lib/Transforms/ 밑에 Obfuscation이라는 폴더가 있고 CMakeLists.txt에서 해당 디렉토리를 add_subdirectory() 를 이용해 추가해주면 됩니다

우리가 새로운 모듈을 제작할때도 예를들어 CustomModules라는 디렉토리를 만들어서 코드를 작성하고, lib/Transforms/CMakeLists.txt에 add_subdirectory(CustomModules)를 추가하면 됩니다



⚡ root@vultr  ~/diffLLVM  diff llvm/lib/Transforms/IPO/LLVMBuild.txt obfuscator/lib/Transforms/IPO/LLVMBuild.txt

23c23

< required_libraries = Analysis Core IPA InstCombine Scalar Support Target TransformUtils Vectorize

---

> required_libraries = Analysis Core IPA InstCombine Scalar Support Target TransformUtils Vectorize Obfuscation

 ✘ ⚡ root@vultr  ~/diffLLVM 

여기서는 required_libraries라는 줄에 Obfuscation이 있냐 없냐가 차이인데,

여기서 한가지 설명이 필요합니다

LLVM은 아주 다양한 용도로 사용되고 있기 때문에 각 상황마다 꼭 필요한 기능이 있고 전혀 필요없는 기능이 있습니다

그래서 각각 기능들은 개별적인 라이브러리 파일로 빌드가 되고

LLVM은 이런 의존성(required_libraries 등등)을 검사해 필요한 라이브러리만 링크시킵니다

여기서 required_libraries에 Obfuscation이 없다면, 빌드시에는 헤더를 잘 참조하기 때문에 문제가 생기지 않지만 링크과정에서 라이브러리가 링크되지 않기 때문에 undefined references for symbol "llvm obfuscator에서 사용하는 함수" 라는 에러가 발생하게 됩니다

우리도 모듈을 개발하면 여기에 의존성을 명시해야 합니다



다음 차이입니다



(사진이 커 생략됨)

✘ ⚡ root@vultr  ~/diffLLVM  diff llvm/lib/Transforms/IPO/PassManagerBuilder.cpp obfuscator/lib/Transforms/IPO/PassManagerBuilder.cpp

31a32,37

> #include "llvm/Transforms/Obfuscation/BogusControlFlow.h"

> #include "llvm/Transforms/Obfuscation/Flattening.h"

> #include "llvm/Transforms/Obfuscation/Split.h"

> #include "llvm/Transforms/Obfuscation/Substitution.h"

> #include "llvm/CryptoUtils.h"

>

80a87,102

> // Flags for obfuscation

> static cl::opt<bool> Flattening("fla", cl::init(false),

>                                 cl::desc("Enable the flattening pass"));

>

> static cl::opt<bool> BogusControlFlow("bcf", cl::init(false),

>                                       cl::desc("Enable bogus control flow"));

>

> static cl::opt<bool> Substitution("sub", cl::init(false),

>                                   cl::desc("Enable instruction substitutions"));

>

> static cl::opt<std::string> AesSeed("aesSeed", cl::init(""),

>                                     cl::desc("seed for the AES-CTR PRNG"));

>

> static cl::opt<bool> Split("spli", cl::init(false),

>                            cl::desc("Enable basic block splitting"));

>

98a121,126

>

>     // Initialization of the global cryptographically

>     // secure pseudo-random generator

>     if(!AesSeed.empty()) {

>         llvm::cryptoutils->prng_seed(AesSeed.c_str());

>     }

161a190,193

>   MPM.add(createSplitBasicBlock(Split));

>   MPM.add(createBogus(BogusControlFlow));

>   MPM.add(createFlattening(Flattening));

>

179a212

>     MPM.add(createSubstitution(Substitution));

373a407

>   MPM.add(createSubstitution(Substitution));

이번엔 모두 필요합니다

우선 맨처음에 include하는것은 자명합니다

그 다음


> // Flags for obfuscation

> static cl::opt<bool> Flattening("fla", cl::init(false),

>                                 cl::desc("Enable the flattening pass"));

>

> static cl::opt<bool> BogusControlFlow("bcf", cl::init(false),

>                                       cl::desc("Enable bogus control flow"));

>

> static cl::opt<bool> Substitution("sub", cl::init(false),

>                                   cl::desc("Enable instruction substitutions"));

>

> static cl::opt<std::string> AesSeed("aesSeed", cl::init(""),

>                                     cl::desc("seed for the AES-CTR PRNG"));

>

> static cl::opt<bool> Split("spli", cl::init(false),

>                            cl::desc("Enable basic block splitting"));

이렇게 flag를 선언해주는데, 첫번재 인자는 커맨드라인 명령이고, 두번째는 초기값, 세번째는 플래그에 대한 설명입니다

Flattening을 초기값(cl::init(false))에서 true로 변경하고 싶다면 -mllvm -fla나 -mllvm -fla=1 을 컴파일 시 추가 하면 됩니다.

Flattening, BogusControlFlow, Substitution 등은 모두 플래그의 값을 갖는 스태틱변수로, 플래그에 해당하는 각각의 Pass들을 실행할지 말지 결정할 수 있습니다


이 플래그들은 바로 그 다음 diff에서 보이는 코드에서 사용됩니다

>

>     // Initialization of the global cryptographically

>     // secure pseudo-random generator

>     if(!AesSeed.empty()) {

>         llvm::cryptoutils->prng_seed(AesSeed.c_str());

>     }

161a190,193

>   MPM.add(createSplitBasicBlock(Split));

>   MPM.add(createBogus(BogusControlFlow));

>   MPM.add(createFlattening(Flattening));

>

179a212

>     MPM.add(createSubstitution(Substitution));

373a407

>   MPM.add(createSubstitution(Substitution));

이 코드들은 지금 diff한 파일인

lib/Transforms/IPO/PassManagerBuilder.cpp 의 populateModulePassManager 함수에 있습니다

여기서는 플래그들을 인자로 넘겨 해당 함수에서 플래그값을 이용해 난독화 적용 여부를 결정합니다



마지막으로 아래 diff 결과는 직접 어떻게 다른지 확인해보시길 바랍니다

Files llvm/lib/Transforms/LLVMBuild.txt and obfuscator/lib/Transforms/LLVMBuild.txt differ

Files llvm/lib/Transforms/Makefile and obfuscator/lib/Transforms/Makefile differ

Only in obfuscator/lib/Transforms: Obfuscation

특히 맨아래의 Only in obfuscator/lib/Transforms: Obfuscation은 LLVM Obfuscator 모듈의 실제 구현 파일들이 존재합니다




중요한 포인트를 정리하자면

1. lib/Transforms/ 밑에 우리 디렉토리를 만들어 작업한다

2. lib/Transforms/CMakeLists.txt 와 lib/Transforms/LLVMBuild.txt에 우리 모듈 디렉토리를 추가해준다

3. lib/Transforms/IPO/LLVMBuild.txt 의 required_libraries에 우리 모듈 이름을 추가한다

4. lib/Transforms/IPO/PassManagerBuilder.txt 의 populateModulePassManager 함수에서 우리 모듈을 ModulePassManager에 추가하도록 한다


이번 글의 내용을 꼭 직접 하나씩 확인해보시면서 진행하시길 권합니다. 새로운 모듈을 개발할 때마다 하나씩 까먹고 고생하는 일이 초반에 종종 생깁니다

신고

설정

트랙백

댓글

LLVM 입문

컴퓨터공부/엘엘비엠 2016.07.15 20:19
크리에이티브 커먼즈 라이선스
Creative Commons License

LLVM은 기존의 gcc와 같은 컴파일러가 당시에 너무 모놀리딕한 형태로 개발되어 코드의 재사용이 힘들단 단점을 개선하기 위해 만들어진 정말 기능별로 잘 분리된 컴파일러입니다


(출처 : http://www.aosabook.org/en/llvm.html)

LLVM은 컴파일러의 3요소가 각각 어떤 형태로든 재사용 될 수 있도록 철저히 분리돼있습니다

먼저 frontend에서는 프로그래밍 언어와 같은 추상적인 형태를 Optimizer에서 다루기 쉽도록 CallGraph를 분석하고, 중간언어인 LLVM IR로 변환해줍니다


이런 LLVM IR 형태의 프로그램을 아주 잘 짜여진 LLVM API들을 이용해 최적화를 실행하고, 많은 방식의 코드 변조가 가능해 난독화(LLVM Obfuscator), 개발단계에서의 취약점 탐지(sanitizer류 플러그인들 ex. asan)등이 이 레벨에서 구현됩니다


처리가 끝난 LLVM IR은 이제 Backend로 넘어가고 백엔드에서는 타겟 머신에 맞는 최적화와 더불어 핵심 기능인 실제 머신 코드로 변환하는 역할을 합니다



이제부터 블로그에 정리하고자 하는 내용은 제가 지난 1년간 LLVM을 이용한 프로젝트를 진행하면서 느낀 LLVM을 사용하는 가장 간단한 방법에 대한 것입니다.

저는 컴파일러의 이론적인 부분은 잘 모르기 때문에 LLVM을 사용해 프로그램에 동적으로 기능을 추가하는 과정에 대해 정리하고, 제가 가장 큰 도움을 받은 LLVM Obfuscator 구조를 설명합니다



LLVM 모듈 개발에 관련된 튜토리얼은 구글에 검색해보시면 많이 나옵니다

제가 진행한 프로젝트는 플러그인식 개발이 아닌 LLVM Optimizer 단계 자체에 기능을 적용한것이기 때문에 검색해도 딱히 잘 나오지 않았고, LLVM으로 개발된 프로젝트를 여러개 분석해보니 LLVM Obfuscator가 사용한 방식이 가장 이해하기도 쉬웠고 제 코드에 적용시키기도 간편했습니다


다음 글에서는 LLVM Obfuscator의 구조를 어떻게 파악하는지를 공유하고, 파악된 구조를 활용해 우리가 추가하고자 하는 기능의 뼈대를 잡겠습니다


제가 LLVM을 활용한 프로젝트는 공개하기 난감하기 때문에 약간 가벼운, 

예를 들어 코딩을 하다가 컴파일러에서 이런기능도 지원해줬으면 좋을텐데 싶은 그런 유틸리티성 기능들을 스텝바이 스텝으로 구현해보겠습니다

그리고 각각 기능 구현 글의 마지막에는 제가 그 기능을 직접 구현해보면서 어떤 오류들이 발생했고 이를 어떻게 해결했는지를 정리합니다



p.s.1 제가 쓴 글은 항상 잘못됐을 수 있습니다. 이건 아니다 싶은건 가차 없이 무시하거나 지적해주세요. 

p.s.2 블로그에서 다루는 추가기능들은 https://github.com/cd80/UtilizedLLVM 여기에 통합되어 배포됩니다.

저작자 표시
신고

설정

트랙백

댓글

try-cat.ch hard 갇혔어요!

크리에이티브 커먼즈 라이선스
Creative Commons License

이 문제는 직접 주변값들을 확인해 정석적으로 풀려면 힘들겠지만

조건이 매우 단순해 단순한 조건문으로 풀었다

로컬에서는 행렬을 한번에 다붙여넣는거라 생각해서 코드를 작성했었는데

try-cat.ch에서 테스트할땐 한줄씩 넣길래

코드를 고치기 귀찮아서 약간의 편법을 썼다


m,n = [int(x) for x in raw_input().split(" ")]
raw = ''.join([raw_input()+"\n" for x in range(m)])
a = raw.split("\n")[:-1]
for i in range(len(a)):
    a[i] = a[i].split(" ")
    if a[i][-1] is '':
        a[i] = a[i][:-1]
ans = []
for i in range(0, m):
    for j in range(0, n):
        if i is not 0 and \
            i is not m-1 and \
            j is not 0 and \
            j is not n-1:
                if a[i][j] == '1':
                    print str(i+1)+","+str(j+1)
신고

'컴퓨터공부 > 프로그래밍' 카테고리의 다른 글

try-cat.ch hard 갇혔어요!  (0) 2015.06.05
try-cat.ch hard 큰 숫자로 합치기  (0) 2015.06.05
try-cat.ch hard 연속된 숫자 찾기  (0) 2015.06.05
assert 함수 동작 원리  (0) 2015.05.09
가상화 5월 5일버젼  (1) 2014.05.05
vm설계할때 노트한내용  (0) 2014.03.26

설정

트랙백

댓글

try-cat.ch hard 큰 숫자로 합치기

크리에이티브 커먼즈 라이선스
Creative Commons License

45 456

과 같이 두개의 자연수가 합쳐지고

숫자열을 재조합해 가장큰 자연수를 만드는 문제다

45 456 입력에 대한 출력예시는 65544이다


num = sorted(''.join([x for x in raw_input("").split(" ")]))[::-1]

print ''.join(num)

신고

'컴퓨터공부 > 프로그래밍' 카테고리의 다른 글

try-cat.ch hard 갇혔어요!  (0) 2015.06.05
try-cat.ch hard 큰 숫자로 합치기  (0) 2015.06.05
try-cat.ch hard 연속된 숫자 찾기  (0) 2015.06.05
assert 함수 동작 원리  (0) 2015.05.09
가상화 5월 5일버젼  (1) 2014.05.05
vm설계할때 노트한내용  (0) 2014.03.26

설정

트랙백

댓글

try-cat.ch hard 연속된 숫자 찾기

크리에이티브 커먼즈 라이선스
Creative Commons License
연속된 숫자 찾기
Btn_try
배열에서 가장 긴 연속된 숫자열을 구한다.

정수의 배열이 입력으로 주어졌을 때, 연속된 숫자들로 이루어진 가장 긴 

부분배열을 찾아라

Time Limit : 200ms, Memory Limit : 512kb
10개 미만의 임의의 자연수로 이루어진 배열
입력 샘플

4 5 34 33 32 11 10 31

자연수 배열
출력 샘플

31 32 33 34


ans = []
max = (0, 0)
data = sorted([int(x) for x in raw_input().split(" ")])
cur = 0
i=0
while True:
    if i+cur+1 > len(data)-1:
        break
    while 1:
        if i+cur+1 < len(data) and data[i+cur]+1 == data[i+cur+1]:
            cur += 1
        else:
            break
    if cur:
        ans.append(data[i:i+cur+1])
        if max[0] < cur:
            max = (cur+1, data[i:i+cur+1])
        i += cur-1
    cur = 0
    i += 1
ans = ''.join([str(x)+" " for x in max[1]])
print ans


신고

'컴퓨터공부 > 프로그래밍' 카테고리의 다른 글

try-cat.ch hard 갇혔어요!  (0) 2015.06.05
try-cat.ch hard 큰 숫자로 합치기  (0) 2015.06.05
try-cat.ch hard 연속된 숫자 찾기  (0) 2015.06.05
assert 함수 동작 원리  (0) 2015.05.09
가상화 5월 5일버젼  (1) 2014.05.05
vm설계할때 노트한내용  (0) 2014.03.26

설정

트랙백

댓글

assert 함수 동작 원리

크리에이티브 커먼즈 라이선스
Creative Commons License

assert함수에서 에러메세지를 출력할때 안의 조건문을 출력해주길래

어떤식으로 정보들을 출력하는지 궁금했다


root@LeaveRet_server:/cd80# cat test.c

#include <assert.h>

main(){

int i=10;

assert(i==9);

}

root@LeaveRet_server:/cd80#

위와 같은 코드를 컴파일하고 실행하면

root@LeaveRet_server:/cd80# ./test

test: test.c:4: main: Assertion `i==9' failed.

Aborted (core dumped)

root@LeaveRet_server:/cd80# 

이런 출력이 나온다


디스어셈블 해보면

i = 10;

if(i==9) { goto locret_8048459; }

else{ __assert_fail(assertion, file, line, func); }

이런 형태가 보인다

assert가 __assert_fail로 바껴서 컴파일이 됐는데

glibc-2.19/assert/assert.h를 보면



# define assert(expr) \

  ((expr) \

   ? __ASSERT_VOID_CAST (0) \

   : __assert_fail (__STRING(expr), __FILE__, __LINE__, __ASSERT_FUNCTION))

 

assert는 이렇게 돼있다

이건 위에서 쓴 

if(i==9) { goto locret_8048459; }

else{ __assert_fail(assertion, file, line, func); }

이 if 문과 else문이 다 설명이 되는 코드인데

우선 expr의 결과가 참인지 거짓인지 판별하고 참이라면 assert(expr)의 결과는 void이다

참고↓

#if defined __cplusplus && __GNUC_PREREQ (2,95)

# define __ASSERT_VOID_CAST static_cast<void>

#else

# define __ASSERT_VOID_CAST (void)

#endif 


expr이 만약 거짓이라면 __assert_fail(__STRING(expr), __FILE__, __LINE, __ASSERT_FUNCTION)) 을 호출하는데

__STRING은 glibc-2.19/misc/sys/cdefs.h 에 정의돼있다

#define __STRING(x) #x

이건 그냥 안에 있는 글자들을 문자열로 그대로 바꾸는 역할을한다

__STRING(cd80) 이라고 하면 "cd80" 이 리턴되는것이다

이건 다른 코드를 작성할때도 매우 유용하게 쓰이는 매크로 중 하나다

그러면 assert(i==9) 라고 했을때 어떻게 "i==9" 라는 문자열이 출력됐는지는 명확해진다

__STRING(i==9) 로 전달돼서 "i==9"로 바뀐것이다

나머지는 그냥 printf("%s %s %d\n", __FILE__, __ASSERT_FUNCTION__, __LINE); 해보면 알수있다



신고

'컴퓨터공부 > 프로그래밍' 카테고리의 다른 글

try-cat.ch hard 큰 숫자로 합치기  (0) 2015.06.05
try-cat.ch hard 연속된 숫자 찾기  (0) 2015.06.05
assert 함수 동작 원리  (0) 2015.05.09
가상화 5월 5일버젼  (1) 2014.05.05
vm설계할때 노트한내용  (0) 2014.03.26
가상화 구현  (0) 2014.03.26

설정

트랙백

댓글

MIPS공부 1. hello world

컴퓨터공부/임베디드 2014.12.23 05:12
크리에이티브 커먼즈 라이선스
Creative Commons License




helloworld를 gdb로 한번 열어봤다

mips 어셈블리는 처음보지만 이걸하겠다고 하나하나 구글에 쳐보는건 미친짓인것같아서

경험을 토대로 분석하기로 했다


우선 addiu가 눈에 띈다

아이유 안녕?


addiu sp, sp, -32

sw ra, 28(sp)

sw s8, 24(sp)

move s8 ,sp가

x86의

push ebp

mov esp, ebp

sub ~~, $esp

랑 똑같아보이는걸 보니 저게 프롤로그인것 같다


아마 ra는 return address의 약자일 것이고

s8은 frame pointer일 것같다

sp는 그냥 stack pointer고


우선 특징을 보면 왼쪽의 명령어 주소들이 4바이트단위로 딱딱 떨어지는것을 보아

mips는 RISC구조인것을 알 수 있다

RISC구조인데 주소가 4바이트이니

ARM의 mov.t mov.w 처럼 두번에 걸쳐서 4바이트 값을 다룰것이고

puts함수의 인자를 넣는 부분을 보면

lui v0, 0x40

addiu a0, v0, 2064 를 하는데

명령어 주소들이 0x0040으로 시작하는것으로 보아

바이너리 영역이 매핑된 주소가 0x00400000 일것이고

나는 printf("Hello, World\n");를 하는 프로그램을 작성했으니

Hello, World\n 문자열의 주소도 바이너리 영역의 주소이다

따라서 lui는 arm에서 mov.t처럼 상위 2바이트에 값을 넣는 명령이다





그다음에 함수호출을 하는데 jal명령으로 한다

함수 호출할때 리턴어드레스(0x40065c)는 ra레지스터에 저장될거같은데 한번 체크해보자


ra에 리턴어드레스가 저장된다는것은 맞췄는데 예상했던 0x40065c가 아니라 그 다음 명령을 가르키고 있었다

근데 어차피 move at, at 같은 nop like 명령이 있는것을 봐서 왜그런진 모르겠지만 저렇게 구현돼있나보다


분석하면서 하나 안게 있는데

mips도 arm처럼 ra에 리턴어드레스를 저장하긴 하지만

메인함수 코드를 보면


+4에서 ra를 스택에 저장하고

+36에서 다시 가져오는것을 볼 수 있다

이를 보아 함수내에서 다른함수를 호출하는일이 있으면 ra를 스택에 저장하고

아니면 그냥 코드 실행후 바로 jr ra를 할 것 같다

이건 나중에 체크해보자



함수 호출전에

move s8, sp

를 하고

함수 리턴 후

move sp, s8을 하는데

lw나 lui를 봐서 방향은 오른쪽에서 왼쪽이고

sp를 s8에 넣어 이전함수의 sp위치를 저장하고

move sp, s8로 다시 sp를 복구하는것을 볼 수 있다

s8을 처음에 생각한것처럼 frame pointer라고 생각하면 되는것 같다


hello world는 그렇게 다른게 없으니 여기서 끝

신고

'컴퓨터공부 > 임베디드' 카테고리의 다른 글

MIPS공부 1. hello world  (0) 2014.12.23
임베디드 공부 계획  (0) 2014.12.23

설정

트랙백

댓글


티스토리 툴바