1 /*
2  * Copyright (C) 2015 The Android Open Source Project
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 "read_elf.h"
18 #include "read_apk.h"
19 
20 #include <stdio.h>
21 #include <string.h>
22 #include <sys/stat.h>
23 #include <sys/types.h>
24 
25 #include <algorithm>
26 #include <limits>
27 
28 #include <android-base/file.h>
29 #include <android-base/logging.h>
30 
31 #pragma clang diagnostic push
32 #pragma clang diagnostic ignored "-Wunused-parameter"
33 
34 #include <llvm/ADT/StringRef.h>
35 #include <llvm/Object/Binary.h>
36 #include <llvm/Object/ELFObjectFile.h>
37 #include <llvm/Object/ObjectFile.h>
38 
39 #pragma clang diagnostic pop
40 
41 #include "utils.h"
42 
43 #define ELF_NOTE_GNU "GNU"
44 #define NT_GNU_BUILD_ID 3
45 
46 using namespace simpleperf;
47 
operator <<(std::ostream & os,const ElfStatus & status)48 std::ostream& operator<<(std::ostream& os, const ElfStatus& status) {
49   switch (status) {
50     case ElfStatus::NO_ERROR:
51       os << "No error";
52       break;
53     case ElfStatus::FILE_NOT_FOUND:
54       os << "File not found";
55       break;
56     case ElfStatus::READ_FAILED:
57       os << "Read failed";
58       break;
59     case ElfStatus::FILE_MALFORMED:
60       os << "Malformed file";
61       break;
62     case ElfStatus::NO_SYMBOL_TABLE:
63       os << "No symbol table";
64       break;
65     case ElfStatus::NO_BUILD_ID:
66       os << "No build id";
67       break;
68     case ElfStatus::BUILD_ID_MISMATCH:
69       os << "Build id mismatch";
70       break;
71     case ElfStatus::SECTION_NOT_FOUND:
72       os << "Section not found";
73       break;
74   }
75   return os;
76 }
77 
IsValidElfFileMagic(const char * buf,size_t buf_size)78 bool IsValidElfFileMagic(const char* buf, size_t buf_size) {
79   static const char elf_magic[] = {0x7f, 'E', 'L', 'F'};
80   return (buf_size >= 4u && memcmp(buf, elf_magic, 4) == 0);
81 }
82 
IsValidElfFile(int fd,uint64_t file_offset)83 ElfStatus IsValidElfFile(int fd, uint64_t file_offset) {
84   char buf[4];
85   if (!android::base::ReadFullyAtOffset(fd, buf, 4, file_offset)) {
86     return ElfStatus::READ_FAILED;
87   }
88   return IsValidElfFileMagic(buf, 4) ? ElfStatus::NO_ERROR : ElfStatus::FILE_MALFORMED;
89 }
90 
GetBuildIdFromNoteSection(const char * section,size_t section_size,BuildId * build_id)91 bool GetBuildIdFromNoteSection(const char* section, size_t section_size, BuildId* build_id) {
92   const char* p = section;
93   const char* end = p + section_size;
94   while (p < end) {
95     if (p + 12 >= end) {
96       return false;
97     }
98     uint32_t namesz;
99     uint32_t descsz;
100     uint32_t type;
101     MoveFromBinaryFormat(namesz, p);
102     MoveFromBinaryFormat(descsz, p);
103     MoveFromBinaryFormat(type, p);
104     namesz = Align(namesz, 4);
105     descsz = Align(descsz, 4);
106     if ((type == NT_GNU_BUILD_ID) && (p < end) && (strcmp(p, ELF_NOTE_GNU) == 0)) {
107       const char* desc_start = p + namesz;
108       const char* desc_end = desc_start + descsz;
109       if (desc_start > p && desc_start < desc_end && desc_end <= end) {
110         *build_id = BuildId(p + namesz, descsz);
111         return true;
112       } else {
113         return false;
114       }
115     }
116     p += namesz + descsz;
117   }
118   return false;
119 }
120 
GetBuildIdFromNoteFile(const std::string & filename,BuildId * build_id)121 ElfStatus GetBuildIdFromNoteFile(const std::string& filename, BuildId* build_id) {
122   std::string content;
123   if (!android::base::ReadFileToString(filename, &content)) {
124     return ElfStatus::READ_FAILED;
125   }
126   if (!GetBuildIdFromNoteSection(content.c_str(), content.size(), build_id)) {
127     return ElfStatus::NO_BUILD_ID;
128   }
129   return ElfStatus::NO_ERROR;
130 }
131 
132 struct BinaryWrapper {
133   std::unique_ptr<llvm::MemoryBuffer> buffer;
134   std::unique_ptr<llvm::object::Binary> binary;
135   llvm::object::ObjectFile* obj = nullptr;
136 };
137 
OpenObjectFile(const std::string & filename,uint64_t file_offset,uint64_t file_size,BinaryWrapper * wrapper)138 static ElfStatus OpenObjectFile(const std::string& filename, uint64_t file_offset,
139                                 uint64_t file_size, BinaryWrapper* wrapper) {
140   if (!IsRegularFile(filename)) {
141     return ElfStatus::FILE_NOT_FOUND;
142   }
143   android::base::unique_fd fd = FileHelper::OpenReadOnly(filename);
144   if (fd == -1) {
145     return ElfStatus::READ_FAILED;
146   }
147   if (file_size == 0) {
148     file_size = GetFileSize(filename);
149     if (file_size == 0) {
150       return ElfStatus::READ_FAILED;
151     }
152   }
153   ElfStatus status = IsValidElfFile(fd, file_offset);
154   if (status != ElfStatus::NO_ERROR) {
155     return status;
156   }
157   auto buffer_or_err = llvm::MemoryBuffer::getOpenFileSlice(fd, filename, file_size, file_offset);
158   if (!buffer_or_err) {
159     return ElfStatus::READ_FAILED;
160   }
161   auto binary_or_err = llvm::object::createBinary(buffer_or_err.get()->getMemBufferRef());
162   if (!binary_or_err) {
163     return ElfStatus::READ_FAILED;
164   }
165   wrapper->buffer = std::move(buffer_or_err.get());
166   wrapper->binary = std::move(binary_or_err.get());
167   wrapper->obj = llvm::dyn_cast<llvm::object::ObjectFile>(wrapper->binary.get());
168   if (wrapper->obj == nullptr) {
169     return ElfStatus::FILE_MALFORMED;
170   }
171   return ElfStatus::NO_ERROR;
172 }
173 
OpenObjectFileInMemory(const char * data,size_t size,BinaryWrapper * wrapper)174 static ElfStatus OpenObjectFileInMemory(const char* data, size_t size, BinaryWrapper* wrapper) {
175   auto buffer = llvm::MemoryBuffer::getMemBuffer(llvm::StringRef(data, size));
176   auto binary_or_err = llvm::object::createBinary(buffer->getMemBufferRef());
177   if (!binary_or_err) {
178     return ElfStatus::FILE_MALFORMED;
179   }
180   wrapper->buffer = std::move(buffer);
181   wrapper->binary = std::move(binary_or_err.get());
182   wrapper->obj = llvm::dyn_cast<llvm::object::ObjectFile>(wrapper->binary.get());
183   if (wrapper->obj == nullptr) {
184     return ElfStatus::FILE_MALFORMED;
185   }
186   return ElfStatus::NO_ERROR;
187 }
188 
IsArmMappingSymbol(const char * name)189 bool IsArmMappingSymbol(const char* name) {
190   // Mapping symbols in arm, which are described in "ELF for ARM Architecture" and
191   // "ELF for ARM 64-bit Architecture". The regular expression to match mapping symbol
192   // is ^\$(a|d|t|x)(\..*)?$
193   return name[0] == '$' && strchr("adtx", name[1]) != nullptr && (name[2] == '\0' || name[2] == '.');
194 }
195 
ReadSymbolTable(llvm::object::symbol_iterator sym_begin,llvm::object::symbol_iterator sym_end,const std::function<void (const ElfFileSymbol &)> & callback,bool is_arm,const llvm::object::section_iterator & section_end)196 void ReadSymbolTable(llvm::object::symbol_iterator sym_begin,
197                      llvm::object::symbol_iterator sym_end,
198                      const std::function<void(const ElfFileSymbol&)>& callback,
199                      bool is_arm,
200                      const llvm::object::section_iterator& section_end) {
201   for (; sym_begin != sym_end; ++sym_begin) {
202     ElfFileSymbol symbol;
203     auto symbol_ref = static_cast<const llvm::object::ELFSymbolRef*>(&*sym_begin);
204     // Exclude undefined symbols, otherwise we may wrongly use them as labels in functions.
205     if (symbol_ref->getFlags() & symbol_ref->SF_Undefined) {
206       continue;
207     }
208     llvm::Expected<llvm::object::section_iterator> section_it_or_err = symbol_ref->getSection();
209     if (!section_it_or_err) {
210       continue;
211     }
212     // Symbols in .dynsym section don't have associated section.
213     if (section_it_or_err.get() != section_end) {
214       llvm::StringRef section_name;
215       if (section_it_or_err.get()->getName(section_name) || section_name.empty()) {
216         continue;
217       }
218       if (section_name == ".text") {
219         symbol.is_in_text_section = true;
220       }
221     }
222 
223     llvm::Expected<llvm::StringRef> symbol_name_or_err = symbol_ref->getName();
224     if (!symbol_name_or_err || symbol_name_or_err.get().empty()) {
225       continue;
226     }
227 
228     symbol.name = symbol_name_or_err.get();
229     symbol.vaddr = symbol_ref->getValue();
230     if ((symbol.vaddr & 1) != 0 && is_arm) {
231       // Arm sets bit 0 to mark it as thumb code, remove the flag.
232       symbol.vaddr &= ~1;
233     }
234     symbol.len = symbol_ref->getSize();
235     llvm::object::SymbolRef::Type symbol_type = *symbol_ref->getType();
236     if (symbol_type == llvm::object::SymbolRef::ST_Function) {
237       symbol.is_func = true;
238     } else if (symbol_type == llvm::object::SymbolRef::ST_Unknown) {
239       if (symbol.is_in_text_section) {
240         symbol.is_label = true;
241         if (is_arm) {
242           // Remove mapping symbols in arm.
243           const char* p = (symbol.name.compare(0, linker_prefix.size(), linker_prefix) == 0)
244                               ? symbol.name.c_str() + linker_prefix.size()
245                               : symbol.name.c_str();
246           if (IsArmMappingSymbol(p)) {
247             symbol.is_label = false;
248           }
249         }
250       }
251     }
252 
253     callback(symbol);
254   }
255 }
256 
257 template <class ELFT>
AddSymbolForPltSection(const llvm::object::ELFObjectFile<ELFT> * elf,const std::function<void (const ElfFileSymbol &)> & callback)258 void AddSymbolForPltSection(const llvm::object::ELFObjectFile<ELFT>* elf,
259                             const std::function<void(const ElfFileSymbol&)>& callback) {
260   // We may sample instructions in .plt section if the program
261   // calls functions from shared libraries. Different architectures use
262   // different formats to store .plt section, so it needs a lot of work to match
263   // instructions in .plt section to symbols. As samples in .plt section rarely
264   // happen, and .plt section can hardly be a performance bottleneck, we can
265   // just use a symbol @plt to represent instructions in .plt section.
266   for (auto it = elf->section_begin(); it != elf->section_end(); ++it) {
267     const llvm::object::ELFSectionRef& section_ref = *it;
268     llvm::StringRef section_name;
269     std::error_code err = section_ref.getName(section_name);
270     if (err || section_name != ".plt") {
271       continue;
272     }
273     const auto* shdr = elf->getSection(section_ref.getRawDataRefImpl());
274     if (shdr == nullptr) {
275       return;
276     }
277     ElfFileSymbol symbol;
278     symbol.vaddr = shdr->sh_addr;
279     symbol.len = shdr->sh_size;
280     symbol.is_func = true;
281     symbol.is_label = true;
282     symbol.is_in_text_section = true;
283     symbol.name = "@plt";
284     callback(symbol);
285     return;
286   }
287 }
288 
289 template <class ELFT>
CheckSymbolSections(const llvm::object::ELFObjectFile<ELFT> * elf,bool * has_symtab,bool * has_dynsym)290 void CheckSymbolSections(const llvm::object::ELFObjectFile<ELFT>* elf,
291                          bool* has_symtab, bool* has_dynsym) {
292   *has_symtab = false;
293   *has_dynsym = false;
294   for (auto it = elf->section_begin(); it != elf->section_end(); ++it) {
295     const llvm::object::ELFSectionRef& section_ref = *it;
296     llvm::StringRef section_name;
297     std::error_code err = section_ref.getName(section_name);
298     if (err) {
299       continue;
300     }
301     if (section_name == ".dynsym") {
302       *has_dynsym = true;
303     } else if (section_name == ".symtab") {
304       *has_symtab = true;
305     }
306   }
307 }
308 
309 namespace {
310 
311 template <typename T>
312 class ElfFileImpl {};
313 
314 template <typename ELFT>
315 class ElfFileImpl<llvm::object::ELFObjectFile<ELFT>> : public ElfFile {
316  public:
ElfFileImpl(BinaryWrapper && wrapper,const llvm::object::ELFObjectFile<ELFT> * elf_obj)317   ElfFileImpl(BinaryWrapper&& wrapper, const llvm::object::ELFObjectFile<ELFT>* elf_obj)
318       : wrapper_(std::move(wrapper)), elf_obj_(elf_obj), elf_(elf_obj->getELFFile()) {}
319 
Is64Bit()320   bool Is64Bit() override {
321     return elf_->getHeader()->getFileClass() == llvm::ELF::ELFCLASS64;
322   }
323 
GetMemoryBuffer()324   llvm::MemoryBuffer* GetMemoryBuffer() override {
325     return wrapper_.buffer.get();
326   }
327 
GetProgramHeader()328   std::vector<ElfSegment> GetProgramHeader() override {
329     auto program_headers = elf_->program_headers();
330     std::vector<ElfSegment> segments(program_headers.size());
331     for (size_t i = 0; i < program_headers.size(); i++) {
332       auto& phdr = program_headers[i];
333       segments[i].vaddr = phdr.p_vaddr;
334       segments[i].file_offset = phdr.p_offset;
335       segments[i].file_size = phdr.p_filesz;
336       segments[i].is_executable =
337           (phdr.p_type == llvm::ELF::PT_LOAD) && (phdr.p_flags & llvm::ELF::PF_X);
338     }
339     return segments;
340   }
341 
GetBuildId(BuildId * build_id)342   ElfStatus GetBuildId(BuildId* build_id) override {
343     llvm::StringRef data = elf_obj_->getData();
344     const char* binary_start = data.data();
345     const char* binary_end = data.data() + data.size();
346     for (auto it = elf_obj_->section_begin(); it != elf_obj_->section_end(); ++it) {
347       const llvm::object::ELFSectionRef& section_ref = *it;
348       if (section_ref.getType() == llvm::ELF::SHT_NOTE) {
349         if (it->getContents(data)) {
350           return ElfStatus::READ_FAILED;
351         }
352         if (data.data() < binary_start || data.data() + data.size() > binary_end) {
353           return ElfStatus::NO_BUILD_ID;
354         }
355         if (GetBuildIdFromNoteSection(data.data(), data.size(), build_id)) {
356           return ElfStatus::NO_ERROR;
357         }
358       }
359     }
360     return ElfStatus::NO_BUILD_ID;
361   }
362 
ParseSymbols(const ParseSymbolCallback & callback)363   ElfStatus ParseSymbols(const ParseSymbolCallback& callback) override {
364     auto machine = elf_->getHeader()->e_machine;
365     bool is_arm = (machine == llvm::ELF::EM_ARM || machine == llvm::ELF::EM_AARCH64);
366     AddSymbolForPltSection(elf_obj_, callback);
367     // Some applications deliberately ship elf files with broken section tables.
368     // So check the existence of .symtab section and .dynsym section before reading symbols.
369     bool has_symtab;
370     bool has_dynsym;
371     CheckSymbolSections(elf_obj_, &has_symtab, &has_dynsym);
372     if (has_symtab && elf_obj_->symbol_begin() != elf_obj_->symbol_end()) {
373       ReadSymbolTable(elf_obj_->symbol_begin(), elf_obj_->symbol_end(), callback, is_arm,
374                       elf_obj_->section_end());
375       return ElfStatus::NO_ERROR;
376     } else if (has_dynsym && elf_obj_->dynamic_symbol_begin()->getRawDataRefImpl() !=
377                                  llvm::object::DataRefImpl()) {
378       ReadSymbolTable(elf_obj_->dynamic_symbol_begin(), elf_obj_->dynamic_symbol_end(), callback,
379                       is_arm, elf_obj_->section_end());
380     }
381     std::string debugdata;
382     ElfStatus result = ReadSection(".gnu_debugdata", &debugdata);
383     if (result == ElfStatus::SECTION_NOT_FOUND) {
384       return ElfStatus::NO_SYMBOL_TABLE;
385     } else if (result == ElfStatus::NO_ERROR) {
386       std::string decompressed_data;
387       if (XzDecompress(debugdata, &decompressed_data)) {
388         auto debugdata_elf =
389             ElfFile::Open(decompressed_data.data(), decompressed_data.size(), &result);
390         if (debugdata_elf) {
391           return debugdata_elf->ParseSymbols(callback);
392         }
393       }
394     }
395     return result;
396   }
397 
ParseDynamicSymbols(const ParseSymbolCallback & callback)398   void ParseDynamicSymbols(const ParseSymbolCallback& callback) override {
399     auto machine = elf_->getHeader()->e_machine;
400     bool is_arm = (machine == llvm::ELF::EM_ARM || machine == llvm::ELF::EM_AARCH64);
401     ReadSymbolTable(elf_obj_->dynamic_symbol_begin(), elf_obj_->dynamic_symbol_end(), callback,
402                     is_arm, elf_obj_->section_end());
403   }
404 
ReadSection(const std::string & section_name,std::string * content)405   ElfStatus ReadSection(const std::string& section_name, std::string* content) override {
406     for (llvm::object::section_iterator it = elf_obj_->section_begin();
407          it != elf_obj_->section_end(); ++it) {
408       llvm::StringRef name;
409       if (it->getName(name) || name != section_name) {
410         continue;
411       }
412       llvm::StringRef data;
413       std::error_code err = it->getContents(data);
414       if (err) {
415         return ElfStatus::READ_FAILED;
416       }
417       *content = data;
418       return ElfStatus::NO_ERROR;
419     }
420     return ElfStatus::SECTION_NOT_FOUND;
421   }
422 
ReadMinExecutableVaddr(uint64_t * file_offset)423   uint64_t ReadMinExecutableVaddr(uint64_t* file_offset) {
424     bool has_vaddr = false;
425     uint64_t min_addr = std::numeric_limits<uint64_t>::max();
426     for (auto it = elf_->program_header_begin(); it != elf_->program_header_end(); ++it) {
427       if ((it->p_type == llvm::ELF::PT_LOAD) && (it->p_flags & llvm::ELF::PF_X)) {
428         if (it->p_vaddr < min_addr) {
429           min_addr = it->p_vaddr;
430           *file_offset = it->p_offset;
431           has_vaddr = true;
432         }
433       }
434     }
435     if (!has_vaddr) {
436       // JIT symfiles don't have program headers.
437       min_addr = 0;
438       *file_offset = 0;
439     }
440     return min_addr;
441   }
442 
VaddrToOff(uint64_t vaddr,uint64_t * file_offset)443   bool VaddrToOff(uint64_t vaddr, uint64_t* file_offset) override {
444     for (auto ph = elf_->program_header_begin(); ph != elf_->program_header_end(); ++ph) {
445       if (ph->p_type == llvm::ELF::PT_LOAD && vaddr >= ph->p_vaddr &&
446           vaddr < ph->p_vaddr + ph->p_filesz) {
447         *file_offset = vaddr - ph->p_vaddr + ph->p_offset;
448         return true;
449       }
450     }
451     return false;
452   }
453 
454  private:
455   BinaryWrapper wrapper_;
456   const llvm::object::ELFObjectFile<ELFT>* elf_obj_;
457   const llvm::object::ELFFile<ELFT>* elf_;
458 };
459 
CreateElfFileImpl(BinaryWrapper && wrapper,ElfStatus * status)460 std::unique_ptr<ElfFile> CreateElfFileImpl(BinaryWrapper&& wrapper, ElfStatus* status) {
461   if (auto obj = llvm::dyn_cast<llvm::object::ELF32LEObjectFile>(wrapper.obj)) {
462     return std::unique_ptr<ElfFile>(
463         new ElfFileImpl<llvm::object::ELF32LEObjectFile>(std::move(wrapper), obj));
464   }
465   if (auto obj = llvm::dyn_cast<llvm::object::ELF64LEObjectFile>(wrapper.obj)) {
466     return std::unique_ptr<ElfFile>(
467         new ElfFileImpl<llvm::object::ELF64LEObjectFile>(std::move(wrapper), obj));
468   }
469   *status = ElfStatus::FILE_MALFORMED;
470   return nullptr;
471 }
472 
473 }  // namespace
474 
475 namespace simpleperf {
476 
Open(const std::string & filename)477 std::unique_ptr<ElfFile> ElfFile::Open(const std::string& filename) {
478   ElfStatus status;
479   auto elf = Open(filename, &status);
480   if (!elf) {
481     LOG(ERROR) << "failed to open " << filename << ": " << status;
482   }
483   return elf;
484 }
485 
Open(const std::string & filename,const BuildId * expected_build_id,ElfStatus * status)486 std::unique_ptr<ElfFile> ElfFile::Open(const std::string& filename,
487                                        const BuildId* expected_build_id, ElfStatus* status) {
488   BinaryWrapper wrapper;
489   auto tuple = SplitUrlInApk(filename);
490   if (std::get<0>(tuple)) {
491     EmbeddedElf* elf = ApkInspector::FindElfInApkByName(std::get<1>(tuple), std::get<2>(tuple));
492     if (elf == nullptr) {
493       *status = ElfStatus::FILE_NOT_FOUND;
494     } else {
495       *status = OpenObjectFile(elf->filepath(), elf->entry_offset(), elf->entry_size(), &wrapper);
496     }
497   } else if (size_t colon_pos = filename.find(':'); colon_pos != std::string::npos) {
498     // path generated by JITDebugReader: app_jit_cache:<file_start>-<file_end>
499     uint64_t file_start;
500     uint64_t file_end;
501     if (sscanf(filename.data() + colon_pos, ":%" PRIu64 "-%" PRIu64, &file_start, &file_end) != 2) {
502       *status = ElfStatus::FILE_NOT_FOUND;
503       return nullptr;
504     }
505     *status =
506         OpenObjectFile(filename.substr(0, colon_pos), file_start, file_end - file_start, &wrapper);
507   } else {
508     *status = OpenObjectFile(filename, 0, 0, &wrapper);
509   }
510   if (*status != ElfStatus::NO_ERROR) {
511     return nullptr;
512   }
513   auto elf = CreateElfFileImpl(std::move(wrapper), status);
514   if (elf && expected_build_id != nullptr && !expected_build_id->IsEmpty()) {
515     BuildId real_build_id;
516     *status = elf->GetBuildId(&real_build_id);
517     if (*status != ElfStatus::NO_ERROR) {
518       return nullptr;
519     }
520     if (*expected_build_id != real_build_id) {
521       *status = ElfStatus::BUILD_ID_MISMATCH;
522       return nullptr;
523     }
524   }
525   return elf;
526 }
527 
Open(const char * data,size_t size,ElfStatus * status)528 std::unique_ptr<ElfFile> ElfFile::Open(const char* data, size_t size, ElfStatus* status) {
529   BinaryWrapper wrapper;
530   *status = OpenObjectFileInMemory(data, size, &wrapper);
531   if (*status != ElfStatus::NO_ERROR) {
532     return nullptr;
533   }
534   return CreateElfFileImpl(std::move(wrapper), status);
535 }
536 
537 }  // namespace simpleperf
538 
539 
540 // LLVM libraries uses ncurses library, but that isn't needed by simpleperf.
541 // So support a naive implementation to avoid depending on ncurses.
setupterm(char *,int,int *)542 __attribute__((weak)) extern "C" int setupterm(char *, int, int *) {
543   return -1;
544 }
545 
set_curterm(struct term *)546 __attribute__((weak)) extern "C" struct term *set_curterm(struct term *) {
547   return nullptr;
548 }
549 
del_curterm(struct term *)550 __attribute__((weak)) extern "C" int del_curterm(struct term *) {
551   return -1;
552 }
553 
tigetnum(char *)554 __attribute__((weak)) extern "C" int tigetnum(char *) {
555   return -1;
556 }
557