본문 바로가기

CNUproject/코드 동일성 검사 도구

16_LLVM 플러그인 적용 오류 해결

저번주에 C코드 파일에 함수 이름을 적용하는 LLVM pass를 적용시키는 것을 시도했다.

하지만 패스가 작성된 cpp 파일을 컴파일하고 링킹하는데에 문제가 있었다.

 

해당 문제는 다음과 같았다.

clang++ -shared -o NamePrinter.dylib NamePrinter.o

이 명령어를 수행했을 때

 

ld: symbol(s) not found for architecture arm64

clang-16: error: linker command failed with exit code 1 (use -v to see invocation)

라는 문구와 함께 에러가 발생한 것을 볼 수 있다.

 

 

내 경우 해결방법은 llvm 프로젝트 안에 내장 패스처럼 해당 패스를 추가해주는 것이다.

 

1. 다운 받았던 llvm 프로젝트에서 llvm/lib/Transforms에 패스를 넣을 폴더(NamePrinter)를 추가하고, 이 폴더(llvm/lib/Transforms/NamePrinter)에 패스가 작성된 cpp 파일과 헤더파일을 넣어준다.

그리고 CMakeLists.txt 를 생성하여 해당 내용을 저장한다.

add_compile_options(-fexceptions)
 add_llvm_library(NamePrinter MODULE
 NamePrinter.cpp
 DEPENDS
 intrinsics_gen
 PLUGIN_TOOL
 opt
)

패스들 간의 dependency를 설정할 수 있다.

 

 

2. llvm/lib/Transform에 있는 CMakeLists.txt에 다음 내용을 추가한다.

add_subdirectory(폴더명)

 

 

3. llvm이 수정됐으므로 build 폴더로 이동하여 다시 빌드를 진행한다. (참고 : https://blueee.tistory.com/15)

 

 

4. 빌드가 완료되면 build/lib/Transforms/NamePrinter 로 이동해서 make 해준다. 그럼 build/lib 에 NamePrinter.dylib이 생성된 걸 확인할 수 있다.

 

 

5. 이제 함수 내용을 출력하는 패스를 적용할 C파일을 생성한다.

나는 간단하게 인자를 전달받아 더해서 리턴하는 add 함수와 main 함수로 이루어진 코드를 작성하였다.

#include <stdio.h>

int add(int x, int y) {
	return x + y;
}

int main() {
	return add(1, 2);
}

 

 

6. 해당 명령어를 통해 bc 파일을 생성한다. 이 명령어는 test.c 파일을 clang 컴파일러를 사용하여 LLVM 비트코드로 컴파일하고 결과를 test.bc에 저장하는 역할을 한다.

clang -c -emit-llvm test.c -o test.bc

 

 

6-1. 이때, clang과 llvm 버전이 같아야 한다.

나는 Homebrew를 통해 설치한 clang이 16.0.6 이고, 설치한 llvm은 12.0.0 버전이어서 나중에 패스 적용할 때

error: Invalid value (Producer: 'LLVM16.0.6' Reader: 'LLVM 12.0.0')

이런 에러가 발생했다. 

 

clang 버전을 확인할 수 있는 방법은 다음과 같다.

clang --version

 

 

6-2. 버전이 서로 다르다면 맞춰줘야 한다.

clang 버전을 낮추기 위해 해당 명령어를 사용하고, 재부팅한다.

brew link llvm@12

재부팅한 후 clang 버전을 다시 확인해보면 12.0.0으로 맞춰져있는 것을 볼 수 있다.

 

 

7. 해당 명령어를 사용하여 패스를 적용해준다.

./build/bin/opt -load ./build/lib/NamePrinter.dylib -NamePrinter test.bc -o out.bc

명령어를 실행하면 함수 이름이 잘 출력되는 것을 확인할 수 있다.

 


 

사용한 파일 목록

 

llvm/lib/Transforms/NamePrinter/NamePrinter.cpp

//===- Hello.cpp - Example code from "Writing an LLVM Pass" ---------------===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// This file implements two versions of the LLVM "Hello World" pass described
// in docs/WritingAnLLVMPass.html
//
//===----------------------------------------------------------------------===//

#include "llvm/Support/raw_ostream.h"
#include "llvm/Support/Debug.h"
#include "NamePrinter.h"

#define DEBUG_TYPE "hello"

bool NamePrinter::runOnFunction(Function &F) {
  dbgs() <<"Hello: "<< F.getName()<<"\n";
  return false;
}

void NamePrinter::getAnalysisUsage(AnalysisUsage &AU) const {
  AU.setPreservesAll();
}

char NamePrinter::ID = 0;
static RegisterPass<NamePrinter> Y("NamePrinter", "Hello World Pass ");

llvm/lib/Transforms/NamePrinter/NamePrinter.h

#ifndef LLVM_TUTORIAL_OPTIMIZATION_TEMPLATE_HELLO_FUNCTION_H
#define LLVM_TUTORIAL_OPTIMIZATION_TEMPLATE_HELLO_FUNCTION_H

#include "llvm/IR/Function.h"
#include "llvm/Pass.h"

using namespace llvm;

namespace {
  struct NamePrinter : public FunctionPass {
    static char ID; // Pass identification, replacement for typeid
    NamePrinter() : FunctionPass(ID) {}

    bool runOnFunction(Function &M) override;

    void getAnalysisUsage(AnalysisUsage &AU) const override;
  };
}

#endif

test.c

#include <stdio.h>

int add(int x, int y) {
	return x + y;
}

int main() {
	return add(1, 2);
}

clang의 버전을 낮추지 않고 llvm 프로젝트 내에 있는 clang 을 사용하여 컴파일하면 가능할 것 같은데 

baekyumi@YUM CNUProject % ./build/bin/clang -c -emit-llvm test.c -o test.bc
test.c:1:10: fatal error: 'stdio.h' file not found
#include <stdio.h>
         ^~~~~~~~~
1 error generated.

헤더파일 에러가 발생한다. 이 부분은 더 찾아봐야 할 것 같다.