• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2015 PLUMgrid, Inc.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include <map>
18 #include <string>
19 #include <algorithm>
20 #include <fcntl.h>
21 #include <ftw.h>
22 #include <map>
23 #include <stdlib.h>
24 #include <stdio.h>
25 #include <string>
26 #include <sys/stat.h>
27 #include <sys/types.h>
28 #include <sys/utsname.h>
29 #include <unistd.h>
30 #include <utility>
31 #include <vector>
32 #include <iostream>
33 #include <linux/bpf.h>
34 
35 #include <clang/Basic/FileManager.h>
36 #include <clang/Basic/TargetInfo.h>
37 #include <clang/CodeGen/BackendUtil.h>
38 #include <clang/CodeGen/CodeGenAction.h>
39 #include <clang/Driver/Compilation.h>
40 #include <clang/Driver/Driver.h>
41 #include <clang/Driver/Job.h>
42 #include <clang/Driver/Tool.h>
43 #include <clang/Frontend/CompilerInstance.h>
44 #include <clang/Frontend/CompilerInvocation.h>
45 #include <clang/Frontend/FrontendActions.h>
46 #include <clang/Frontend/FrontendDiagnostic.h>
47 #include <clang/Frontend/TextDiagnosticPrinter.h>
48 #include <clang/FrontendTool/Utils.h>
49 #include <clang/Lex/PreprocessorOptions.h>
50 
51 #include <llvm/IR/Module.h>
52 
53 #include "bcc_exception.h"
54 #include "bpf_module.h"
55 #include "exported_files.h"
56 #include "kbuild_helper.h"
57 #include "b_frontend_action.h"
58 #include "tp_frontend_action.h"
59 #include "loader.h"
60 #include "arch_helper.h"
61 
62 using std::map;
63 using std::string;
64 using std::unique_ptr;
65 using std::vector;
66 
67 namespace ebpf {
68 
get_func(std::string name)69 optional<FuncInfo &> ProgFuncInfo::get_func(std::string name) {
70   auto it = funcs_.find(name);
71   if (it != funcs_.end())
72     return it->second;
73   return nullopt;
74 }
75 
get_func(size_t id)76 optional<FuncInfo &> ProgFuncInfo::get_func(size_t id) {
77   auto it = func_idx_.find(id);
78   if (it != func_idx_.end())
79     return get_func(it->second);
80   return nullopt;
81 }
82 
func_name(size_t id)83 optional<std::string &> ProgFuncInfo::func_name(size_t id) {
84   auto it = func_idx_.find(id);
85   if (it != func_idx_.end())
86     return it->second;
87   return nullopt;
88 }
89 
for_each_func(std::function<void (std::string,FuncInfo &)> cb)90 void ProgFuncInfo::for_each_func(
91     std::function<void(std::string, FuncInfo &)> cb) {
92   for (auto it = funcs_.begin(); it != funcs_.end(); ++it) {
93     cb(it->first, it->second);
94   }
95 }
96 
add_func(std::string name)97 optional<FuncInfo &> ProgFuncInfo::add_func(std::string name) {
98   auto fn = get_func(name);
99   if (fn)
100     return nullopt;
101   size_t current = funcs_.size();
102   funcs_.emplace(name, 0);
103   func_idx_.emplace(current, name);
104   return get_func(name);
105 }
106 
ClangLoader(llvm::LLVMContext * ctx,unsigned flags)107 ClangLoader::ClangLoader(llvm::LLVMContext *ctx, unsigned flags)
108     : ctx_(ctx), flags_(flags)
109 {
110   for (auto f : ExportedFiles::headers())
111     remapped_headers_[f.first] = llvm::MemoryBuffer::getMemBuffer(f.second);
112   for (auto f : ExportedFiles::footers())
113     remapped_footers_[f.first] = llvm::MemoryBuffer::getMemBuffer(f.second);
114 }
115 
~ClangLoader()116 ClangLoader::~ClangLoader() {}
117 
add_remapped_includes(clang::CompilerInvocation & invocation)118 void ClangLoader::add_remapped_includes(clang::CompilerInvocation& invocation)
119 {
120   // This option instructs clang whether or not to free the file buffers that we
121   // give to it. Since the embedded header files should be copied fewer times
122   // and reused if possible, set this flag to true.
123   invocation.getPreprocessorOpts().RetainRemappedFileBuffers = true;
124   for (const auto &f : remapped_headers_)
125     invocation.getPreprocessorOpts().addRemappedFile(f.first, &*f.second);
126   for (const auto &f : remapped_footers_)
127     invocation.getPreprocessorOpts().addRemappedFile(f.first, &*f.second);
128 }
129 
add_main_input(clang::CompilerInvocation & invocation,const std::string & main_path,llvm::MemoryBuffer * main_buf)130 void ClangLoader::add_main_input(clang::CompilerInvocation& invocation,
131                                  const std::string& main_path,
132                                  llvm::MemoryBuffer *main_buf)
133 {
134   invocation.getPreprocessorOpts().addRemappedFile(main_path, main_buf);
135   invocation.getFrontendOpts().Inputs.clear();
136   invocation.getFrontendOpts().Inputs.push_back(
137       clang::FrontendInputFile(
138         main_path,
139         clang::FrontendOptions::getInputKindForExtension("c"))
140   );
141 }
142 
143 namespace
144 {
145 
is_dir(const string & path)146 bool is_dir(const string& path)
147 {
148   struct stat buf;
149 
150   if (::stat (path.c_str (), &buf) < 0)
151     return false;
152 
153   return S_ISDIR(buf.st_mode);
154 }
155 
is_file(const string & path)156 bool is_file(const string& path)
157 {
158   struct stat buf;
159 
160   if (::stat (path.c_str (), &buf) < 0)
161     return false;
162 
163   return S_ISREG(buf.st_mode);
164 }
165 
get_kernel_path_info(const string kdir)166 std::pair<bool, string> get_kernel_path_info(const string kdir)
167 {
168   if (is_dir(kdir + "/build") && is_dir(kdir + "/source"))
169     return std::make_pair (true, "source");
170 
171   const char* suffix_from_env = ::getenv("BCC_KERNEL_MODULES_SUFFIX");
172   if (suffix_from_env)
173     return std::make_pair(false, string(suffix_from_env));
174 
175   return std::make_pair(false, "build");
176 }
177 
CreateFromArgs(clang::CompilerInvocation & invocation,const llvm::opt::ArgStringList & ccargs,clang::DiagnosticsEngine & diags)178 static int CreateFromArgs(clang::CompilerInvocation &invocation,
179                           const llvm::opt::ArgStringList &ccargs,
180                           clang::DiagnosticsEngine &diags)
181 {
182 #if LLVM_MAJOR_VERSION >= 10
183   return clang::CompilerInvocation::CreateFromArgs(invocation, ccargs, diags);
184 #else
185   return clang::CompilerInvocation::CreateFromArgs(
186               invocation, const_cast<const char **>(ccargs.data()),
187               const_cast<const char **>(ccargs.data()) + ccargs.size(), diags);
188 #endif
189 }
190 
191 }
192 
parse(unique_ptr<llvm::Module> * mod,TableStorage & ts,const string & file,bool in_memory,const char * cflags[],int ncflags,const std::string & id,ProgFuncInfo & prog_func_info,std::string & mod_src,const std::string & maps_ns,fake_fd_map_def & fake_fd_map,std::map<std::string,std::vector<std::string>> & perf_events)193 int ClangLoader::parse(
194     unique_ptr<llvm::Module> *mod, TableStorage &ts, const string &file,
195     bool in_memory, const char *cflags[], int ncflags, const std::string &id,
196     ProgFuncInfo &prog_func_info, std::string &mod_src,
197     const std::string &maps_ns, fake_fd_map_def &fake_fd_map,
198     std::map<std::string, std::vector<std::string>> &perf_events) {
199   string main_path = "/virtual/main.c";
200   unique_ptr<llvm::MemoryBuffer> main_buf;
201   struct utsname un;
202   uname(&un);
203   string kdir, kpath;
204   const char *kpath_env = ::getenv("BCC_KERNEL_SOURCE");
205   const char *version_override = ::getenv("BCC_LINUX_VERSION_CODE");
206   bool has_kpath_source = false;
207   string vmacro;
208   std::string tmpdir;
209 
210   if (kpath_env) {
211     kpath = string(kpath_env);
212   } else {
213     kdir = string(KERNEL_MODULES_DIR) + "/" + un.release;
214     auto kernel_path_info = get_kernel_path_info(kdir);
215     has_kpath_source = kernel_path_info.first;
216     kpath = kdir + "/" + kernel_path_info.second;
217   }
218 
219   // If all attempts to obtain kheaders fail, check for kheaders.tar.xz in sysfs
220   // Checking just for kpath existence is unsufficient, since it can refer to
221   // leftover build directory without headers present anymore.
222   // See https://github.com/iovisor/bcc/pull/3588 for more details.
223   if (!is_file(kpath + "/include/linux/kconfig.h")) {
224     int ret = get_proc_kheaders(tmpdir);
225     if (!ret) {
226       kpath = tmpdir;
227     } else {
228       std::cout << "Unable to find kernel headers. ";
229       std::cout << "Try rebuilding kernel with CONFIG_IKHEADERS=m (module) ";
230       std::cout <<  "or installing the kernel development package for your running kernel version.\n";
231     }
232   }
233 
234   if (flags_ & DEBUG_PREPROCESSOR)
235     std::cout << "Running from kernel directory at: " << kpath.c_str() << "\n";
236 
237   // clang needs to run inside the kernel dir
238   DirStack dstack(kpath);
239   if (!dstack.ok())
240     return -1;
241 
242   string abs_file;
243   if (in_memory) {
244     abs_file = main_path;
245     main_buf = llvm::MemoryBuffer::getMemBuffer(file);
246   } else {
247     if (file.substr(0, 1) == "/")
248       abs_file = file;
249     else
250       abs_file = string(dstack.cwd()) + "/" + file;
251   }
252 
253   // -fno-color-diagnostics: this is a workaround for a bug in llvm terminalHasColors() as of
254   // 22 Jul 2016. Also see bcc #615.
255   // Enable -O2 for clang. In clang 5.0, -O0 may result in function marking as
256   // noinline and optnone (if not always inlining).
257   // Note that first argument is ignored in clang compilation invocation.
258   // "-D __BPF_TRACING__" below is added to suppress a warning in 4.17+.
259   // It can be removed once clang supports asm-goto or the kernel removes
260   // the warning.
261   vector<const char *> flags_cstr({"-O0", "-O2", "-emit-llvm", "-I", dstack.cwd(),
262                                    "-D", "__BPF_TRACING__",
263                                    "-Wno-deprecated-declarations",
264                                    "-Wno-gnu-variable-sized-type-not-at-end",
265                                    "-Wno-pragma-once-outside-header",
266                                    "-Wno-address-of-packed-member",
267                                    "-Wno-unknown-warning-option",
268                                    "-fno-color-diagnostics",
269                                    "-fno-unwind-tables",
270                                    "-fno-asynchronous-unwind-tables",
271                                    "-x", "c", "-c", abs_file.c_str()});
272 
273   const char *arch = getenv("ARCH");
274   if (!arch)
275     arch = un.machine;
276 
277   if (!strncmp(arch, "mips", 4)) {
278     flags_cstr.push_back("-D__MIPSEL__");
279     flags_cstr.push_back("-D_MIPS_SZLONG=64");
280   }
281 
282   KBuildHelper kbuild_helper(kpath_env ? kpath : kdir, has_kpath_source);
283 
284   vector<string> kflags;
285   if (kbuild_helper.get_flags(un.machine, &kflags))
286     return -1;
287 #if LLVM_MAJOR_VERSION >= 9
288   flags_cstr.push_back("-g");
289   flags_cstr.push_back("-gdwarf-4");
290 #else
291   if (flags_ & DEBUG_SOURCE)
292     flags_cstr.push_back("-g");
293 #endif
294   for (auto it = kflags.begin(); it != kflags.end(); ++it)
295     flags_cstr.push_back(it->c_str());
296 
297   vector<const char *> flags_cstr_rem;
298 
299   if (version_override) {
300     vmacro = "-DLINUX_VERSION_CODE_OVERRIDE=" + string(version_override);
301 
302     std::cout << "WARNING: Linux version for eBPF program is being overridden with: " << version_override << "\n";
303     std::cout << "WARNING: Due to this, the results of the program may be unpredictable\n";
304     flags_cstr_rem.push_back(vmacro.c_str());
305   }
306 
307   flags_cstr_rem.push_back("-include");
308   flags_cstr_rem.push_back("/virtual/include/bcc/helpers.h");
309   flags_cstr_rem.push_back("-isystem");
310   flags_cstr_rem.push_back("/virtual/include");
311   if (cflags) {
312     for (auto i = 0; i < ncflags; ++i)
313       flags_cstr_rem.push_back(cflags[i]);
314   }
315 #ifdef CUR_CPU_IDENTIFIER
316   string cur_cpu_flag = string("-DCUR_CPU_IDENTIFIER=") + CUR_CPU_IDENTIFIER;
317   flags_cstr_rem.push_back(cur_cpu_flag.c_str());
318 #endif
319 
320   if (do_compile(mod, ts, in_memory, flags_cstr, flags_cstr_rem, main_path,
321                  main_buf, id, prog_func_info, mod_src, true, maps_ns,
322                  fake_fd_map, perf_events)) {
323 #if BCC_BACKUP_COMPILE != 1
324     return -1;
325 #else
326     // try one more time to compile with system bpf.h
327     llvm::errs() << "WARNING: compilation failure, trying with system bpf.h\n";
328 
329     ts.DeletePrefix(Path({id}));
330     prog_func_info.clear();
331     mod_src.clear();
332     fake_fd_map.clear();
333     if (do_compile(mod, ts, in_memory, flags_cstr, flags_cstr_rem, main_path,
334                    main_buf, id, prog_func_info, mod_src, false, maps_ns,
335                    fake_fd_map, perf_events))
336       return -1;
337 #endif
338   }
339 
340   return 0;
341 }
342 
get_clang_target_cb(bcc_arch_t arch,bool for_syscall)343 void *get_clang_target_cb(bcc_arch_t arch, bool for_syscall)
344 {
345   const char *ret;
346 
347   switch(arch) {
348     case BCC_ARCH_PPC_LE:
349       ret = "powerpc64le-unknown-linux-gnu";
350       break;
351     case BCC_ARCH_PPC:
352       ret = "powerpc64-unknown-linux-gnu";
353       break;
354     case BCC_ARCH_S390X:
355       ret = "s390x-ibm-linux-gnu";
356       break;
357     case BCC_ARCH_ARM64:
358       ret = "aarch64-unknown-linux-gnu";
359       break;
360     case BCC_ARCH_MIPS:
361       ret = "mips64el-unknown-linux-gnuabi64";
362       break;
363     default:
364       ret = "x86_64-unknown-linux-gnu";
365   }
366 
367   return (void *)ret;
368 }
369 
get_clang_target(void)370 string get_clang_target(void) {
371   const char *ret;
372 
373   ret = (const char *)run_arch_callback(get_clang_target_cb);
374   return string(ret);
375 }
376 
do_compile(unique_ptr<llvm::Module> * mod,TableStorage & ts,bool in_memory,const vector<const char * > & flags_cstr_in,const vector<const char * > & flags_cstr_rem,const std::string & main_path,const unique_ptr<llvm::MemoryBuffer> & main_buf,const std::string & id,ProgFuncInfo & prog_func_info,std::string & mod_src,bool use_internal_bpfh,const std::string & maps_ns,fake_fd_map_def & fake_fd_map,std::map<std::string,std::vector<std::string>> & perf_events)377 int ClangLoader::do_compile(
378     unique_ptr<llvm::Module> *mod, TableStorage &ts, bool in_memory,
379     const vector<const char *> &flags_cstr_in,
380     const vector<const char *> &flags_cstr_rem, const std::string &main_path,
381     const unique_ptr<llvm::MemoryBuffer> &main_buf, const std::string &id,
382     ProgFuncInfo &prog_func_info, std::string &mod_src, bool use_internal_bpfh,
383     const std::string &maps_ns, fake_fd_map_def &fake_fd_map,
384     std::map<std::string, std::vector<std::string>> &perf_events) {
385   using namespace clang;
386 
387   vector<const char *> flags_cstr = flags_cstr_in;
388   if (use_internal_bpfh) {
389     flags_cstr.push_back("-include");
390     flags_cstr.push_back("/virtual/include/bcc/bpf.h");
391   }
392   flags_cstr.push_back("-include");
393   flags_cstr.push_back("/virtual/include/bcc/bpf_workaround.h");
394   flags_cstr.insert(flags_cstr.end(), flags_cstr_rem.begin(),
395                     flags_cstr_rem.end());
396 
397   // set up the error reporting class
398   IntrusiveRefCntPtr<DiagnosticOptions> diag_opts(new DiagnosticOptions());
399   auto diag_client = new TextDiagnosticPrinter(llvm::errs(), &*diag_opts);
400 
401   IntrusiveRefCntPtr<DiagnosticIDs> DiagID(new DiagnosticIDs());
402   DiagnosticsEngine diags(DiagID, &*diag_opts, diag_client);
403 
404   // set up the command line argument wrapper
405 
406   string target_triple = get_clang_target();
407   driver::Driver drv("", target_triple, diags);
408 
409 #if LLVM_MAJOR_VERSION >= 4
410   if (target_triple == "x86_64-unknown-linux-gnu" || target_triple == "aarch64-unknown-linux-gnu")
411     flags_cstr.push_back("-fno-jump-tables");
412 #endif
413 
414   drv.setTitle("bcc-clang-driver");
415   drv.setCheckInputsExist(false);
416 
417   unique_ptr<driver::Compilation> compilation(drv.BuildCompilation(flags_cstr));
418   if (!compilation)
419     return -1;
420 
421   // expect exactly 1 job, otherwise error
422   const driver::JobList &jobs = compilation->getJobs();
423   if (jobs.size() != 1 || !isa<driver::Command>(*jobs.begin())) {
424     SmallString<256> msg;
425     llvm::raw_svector_ostream os(msg);
426     jobs.Print(os, "; ", true);
427     diags.Report(diag::err_fe_expected_compiler_job) << os.str();
428     return -1;
429   }
430 
431   const driver::Command &cmd = cast<driver::Command>(*jobs.begin());
432   if (llvm::StringRef(cmd.getCreator().getName()) != "clang") {
433     diags.Report(diag::err_fe_expected_clang_command);
434     return -1;
435   }
436 
437   // Initialize a compiler invocation object from the clang (-cc1) arguments.
438   const llvm::opt::ArgStringList &ccargs = cmd.getArguments();
439 
440   if (flags_ & DEBUG_PREPROCESSOR) {
441     llvm::errs() << "clang";
442     for (auto arg : ccargs)
443       llvm::errs() << " " << arg;
444     llvm::errs() << "\n";
445   }
446 
447   // pre-compilation pass for generating tracepoint structures
448   CompilerInstance compiler0;
449   CompilerInvocation &invocation0 = compiler0.getInvocation();
450   if (!CreateFromArgs(invocation0, ccargs, diags))
451     return -1;
452 
453   add_remapped_includes(invocation0);
454 
455   if (in_memory) {
456     add_main_input(invocation0, main_path, &*main_buf);
457   }
458   invocation0.getFrontendOpts().DisableFree = false;
459 
460   compiler0.createDiagnostics(new IgnoringDiagConsumer());
461 
462   // capture the rewritten c file
463   string out_str;
464   llvm::raw_string_ostream os(out_str);
465   TracepointFrontendAction tpact(os);
466   compiler0.ExecuteAction(tpact); // ignore errors, they will be reported later
467   unique_ptr<llvm::MemoryBuffer> out_buf = llvm::MemoryBuffer::getMemBuffer(out_str);
468 
469   // first pass
470   CompilerInstance compiler1;
471   CompilerInvocation &invocation1 = compiler1.getInvocation();
472   if (!CreateFromArgs( invocation1, ccargs, diags))
473     return -1;
474 
475   add_remapped_includes(invocation1);
476   add_main_input(invocation1, main_path, &*out_buf);
477   invocation1.getFrontendOpts().DisableFree = false;
478 
479   compiler1.createDiagnostics();
480 
481   // capture the rewritten c file
482   string out_str1;
483   llvm::raw_string_ostream os1(out_str1);
484   BFrontendAction bact(os1, flags_, ts, id, main_path, prog_func_info, mod_src,
485                        maps_ns, fake_fd_map, perf_events);
486   if (!compiler1.ExecuteAction(bact))
487     return -1;
488   unique_ptr<llvm::MemoryBuffer> out_buf1 = llvm::MemoryBuffer::getMemBuffer(out_str1);
489 
490   // second pass, clear input and take rewrite buffer
491   CompilerInstance compiler2;
492   CompilerInvocation &invocation2 = compiler2.getInvocation();
493   if (!CreateFromArgs(invocation2, ccargs, diags))
494     return -1;
495 
496   add_remapped_includes(invocation2);
497   add_main_input(invocation2, main_path, &*out_buf1);
498   invocation2.getFrontendOpts().DisableFree = false;
499   invocation2.getCodeGenOpts().DisableFree = false;
500   // Resort to normal inlining. In -O0 the default is OnlyAlwaysInlining and
501   // clang might add noinline attribute even for functions with inline hint.
502   invocation2.getCodeGenOpts().setInlining(CodeGenOptions::NormalInlining);
503   // suppress warnings in the 2nd pass, but bail out on errors (our fault)
504   invocation2.getDiagnosticOpts().IgnoreWarnings = true;
505   compiler2.createDiagnostics();
506 
507   EmitLLVMOnlyAction ir_act(&*ctx_);
508   if (!compiler2.ExecuteAction(ir_act))
509     return -1;
510   *mod = ir_act.takeModule();
511 
512   return 0;
513 }
514 }  // namespace ebpf
515