[elf] Ressurect elf library
Resurrect the existing but unused ELF library in libraries/elf, and use it instead of boot/elf.h for parsing ELF files in the bootloader. Also adds a const version of offset_iterator called const_offset_iterator.
This commit is contained in:
@@ -1,83 +0,0 @@
|
|||||||
/// \file elf.h
|
|
||||||
/// Definitions and related constants for ELF64 structures
|
|
||||||
#pragma once
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
namespace boot {
|
|
||||||
namespace elf {
|
|
||||||
|
|
||||||
constexpr uint8_t version = 1;
|
|
||||||
constexpr uint8_t word_size = 2;
|
|
||||||
constexpr uint8_t endianness = 1;
|
|
||||||
constexpr uint8_t os_abi = 0;
|
|
||||||
constexpr uint16_t machine = 0x3e;
|
|
||||||
|
|
||||||
const unsigned PT_LOAD = 1;
|
|
||||||
const unsigned ST_PROGBITS = 1;
|
|
||||||
const unsigned ST_NOBITS = 8;
|
|
||||||
const unsigned long SHF_ALLOC = 0x2;
|
|
||||||
|
|
||||||
struct header
|
|
||||||
{
|
|
||||||
char magic[4];
|
|
||||||
|
|
||||||
uint8_t word_size;
|
|
||||||
uint8_t endianness;
|
|
||||||
uint8_t header_version;
|
|
||||||
uint8_t os_abi;
|
|
||||||
|
|
||||||
uint64_t reserved;
|
|
||||||
|
|
||||||
uint16_t type;
|
|
||||||
uint16_t machine;
|
|
||||||
|
|
||||||
uint32_t version;
|
|
||||||
|
|
||||||
uint64_t entrypoint;
|
|
||||||
uint64_t ph_offset;
|
|
||||||
uint64_t sh_offset;
|
|
||||||
|
|
||||||
uint32_t flags;
|
|
||||||
|
|
||||||
uint16_t eh_size;
|
|
||||||
|
|
||||||
uint16_t ph_entsize;
|
|
||||||
uint16_t ph_num;
|
|
||||||
|
|
||||||
uint16_t sh_entsize;
|
|
||||||
uint16_t sh_num;
|
|
||||||
|
|
||||||
uint16_t sh_str_idx;
|
|
||||||
} __attribute__ ((packed));
|
|
||||||
|
|
||||||
struct program_header
|
|
||||||
{
|
|
||||||
uint32_t type;
|
|
||||||
uint32_t flags;
|
|
||||||
uint64_t offset;
|
|
||||||
|
|
||||||
uint64_t vaddr;
|
|
||||||
uint64_t paddr;
|
|
||||||
|
|
||||||
uint64_t file_size;
|
|
||||||
uint64_t mem_size;
|
|
||||||
|
|
||||||
uint64_t align;
|
|
||||||
} __attribute__ ((packed));
|
|
||||||
|
|
||||||
struct section_header
|
|
||||||
{
|
|
||||||
uint32_t name;
|
|
||||||
uint32_t type;
|
|
||||||
uint64_t flags;
|
|
||||||
uint64_t addr;
|
|
||||||
uint64_t offset;
|
|
||||||
uint64_t size;
|
|
||||||
uint32_t link;
|
|
||||||
uint32_t info;
|
|
||||||
uint64_t align;
|
|
||||||
uint64_t entry_size;
|
|
||||||
} __attribute__ ((packed));
|
|
||||||
|
|
||||||
} // namespace elf
|
|
||||||
} // namespace boot
|
|
||||||
@@ -3,7 +3,8 @@
|
|||||||
|
|
||||||
#include "allocator.h"
|
#include "allocator.h"
|
||||||
#include "console.h"
|
#include "console.h"
|
||||||
#include "elf.h"
|
#include "elf/file.h"
|
||||||
|
#include "elf/headers.h"
|
||||||
#include "error.h"
|
#include "error.h"
|
||||||
#include "fs.h"
|
#include "fs.h"
|
||||||
#include "init_args.h"
|
#include "init_args.h"
|
||||||
@@ -35,21 +36,6 @@ load_file(
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
static bool
|
|
||||||
is_elfheader_valid(const elf::header *header)
|
|
||||||
{
|
|
||||||
return
|
|
||||||
header->magic[0] == 0x7f &&
|
|
||||||
header->magic[1] == 'E' &&
|
|
||||||
header->magic[2] == 'L' &&
|
|
||||||
header->magic[3] == 'F' &&
|
|
||||||
header->word_size == elf::word_size &&
|
|
||||||
header->endianness == elf::endianness &&
|
|
||||||
header->os_abi == elf::os_abi &&
|
|
||||||
header->machine == elf::machine &&
|
|
||||||
header->header_version == elf::version;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
create_module(buffer data, const program_desc &desc, bool loaded)
|
create_module(buffer data, const program_desc &desc, bool loaded)
|
||||||
{
|
{
|
||||||
@@ -82,55 +68,45 @@ load_program(
|
|||||||
if (add_module)
|
if (add_module)
|
||||||
create_module(data, desc, true);
|
create_module(data, desc, true);
|
||||||
|
|
||||||
const elf::header *header = reinterpret_cast<const elf::header*>(data.pointer);
|
elf::file program(data.pointer, data.count);
|
||||||
uintptr_t program_base = reinterpret_cast<uintptr_t>(data.pointer);
|
if (!program.valid())
|
||||||
|
|
||||||
if (data.count < sizeof(elf::header) || !is_elfheader_valid(header))
|
|
||||||
error::raise(uefi::status::load_error, L"ELF file not valid");
|
error::raise(uefi::status::load_error, L"ELF file not valid");
|
||||||
|
|
||||||
size_t num_sections = 0;
|
size_t num_sections = 0;
|
||||||
for (int i = 0; i < header->ph_num; ++i) {
|
for (auto &seg : program.programs()) {
|
||||||
ptrdiff_t offset = header->ph_offset + i * header->ph_entsize;
|
if (seg.type == elf::segment_type::load)
|
||||||
const elf::program_header *pheader =
|
|
||||||
offset_ptr<elf::program_header>(data.pointer, offset);
|
|
||||||
|
|
||||||
if (pheader->type == elf::PT_LOAD)
|
|
||||||
++num_sections;
|
++num_sections;
|
||||||
}
|
}
|
||||||
|
|
||||||
init::program_section *sections = new init::program_section [num_sections];
|
init::program_section *sections = new init::program_section [num_sections];
|
||||||
|
|
||||||
size_t next_section = 0;
|
size_t next_section = 0;
|
||||||
for (int i = 0; i < header->ph_num; ++i) {
|
for (auto &seg : program.programs()) {
|
||||||
ptrdiff_t offset = header->ph_offset + i * header->ph_entsize;
|
if (seg.type != elf::segment_type::load)
|
||||||
const elf::program_header *pheader =
|
|
||||||
offset_ptr<elf::program_header>(data.pointer, offset);
|
|
||||||
|
|
||||||
if (pheader->type != elf::PT_LOAD)
|
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
init::program_section §ion = sections[next_section++];
|
init::program_section §ion = sections[next_section++];
|
||||||
|
|
||||||
size_t page_count = memory::bytes_to_pages(pheader->mem_size);
|
size_t page_count = memory::bytes_to_pages(seg.mem_size);
|
||||||
|
|
||||||
if (pheader->mem_size > pheader->file_size) {
|
if (seg.mem_size > seg.file_size) {
|
||||||
void *pages = g_alloc.allocate_pages(page_count, alloc_type::program, true);
|
void *pages = g_alloc.allocate_pages(page_count, alloc_type::program, true);
|
||||||
void *source = offset_ptr<void>(data.pointer, pheader->offset);
|
void *source = offset_ptr<void>(data.pointer, seg.offset);
|
||||||
g_alloc.copy(pages, source, pheader->file_size);
|
g_alloc.copy(pages, source, seg.file_size);
|
||||||
section.phys_addr = reinterpret_cast<uintptr_t>(pages);
|
section.phys_addr = reinterpret_cast<uintptr_t>(pages);
|
||||||
} else {
|
} else {
|
||||||
section.phys_addr = program_base + pheader->offset;
|
section.phys_addr = program.base() + seg.offset;
|
||||||
}
|
}
|
||||||
|
|
||||||
section.virt_addr = pheader->vaddr;
|
section.virt_addr = seg.vaddr;
|
||||||
section.size = pheader->mem_size;
|
section.size = seg.mem_size;
|
||||||
section.type = static_cast<init::section_flags>(pheader->flags);
|
section.type = static_cast<init::section_flags>(seg.flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
init::program *prog = new init::program;
|
init::program *prog = new init::program;
|
||||||
prog->sections = { .pointer = sections, .count = num_sections };
|
prog->sections = { .pointer = sections, .count = num_sections };
|
||||||
prog->phys_base = program_base;
|
prog->phys_base = program.base();
|
||||||
prog->entrypoint = header->entrypoint;
|
prog->entrypoint = program.entrypoint();
|
||||||
return prog;
|
return prog;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ name = "boot"
|
|||||||
kind = "exe"
|
kind = "exe"
|
||||||
output = "boot.efi"
|
output = "boot.efi"
|
||||||
targets = ["boot"]
|
targets = ["boot"]
|
||||||
deps = ["cpu", "kutil"]
|
deps = ["cpu", "elf", "kutil"]
|
||||||
sources = [
|
sources = [
|
||||||
"allocator.cpp",
|
"allocator.cpp",
|
||||||
"console.cpp",
|
"console.cpp",
|
||||||
|
|||||||
@@ -10,26 +10,56 @@ inline T* offset_ptr(S* input, ptrdiff_t offset) {
|
|||||||
return reinterpret_cast<T*>(reinterpret_cast<uintptr_t>(input) + offset);
|
return reinterpret_cast<T*>(reinterpret_cast<uintptr_t>(input) + offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Iterator for an array of `T` whose size is known at runtime
|
/// Iterator for an array of `const T` whose size is known at runtime
|
||||||
/// \tparam T Type of the objects in the array, whose size might not be
|
/// \tparam T Type of the objects in the array, whose size might not be
|
||||||
/// what is returned by sizeof(T).
|
/// what is returned by sizeof(T).
|
||||||
template <typename T>
|
template <typename T>
|
||||||
class offset_iterator
|
class const_offset_iterator
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
/// Constructor.
|
/// Constructor.
|
||||||
/// \arg t Pointer to the first item in the array
|
/// \arg t Pointer to the first item in the array
|
||||||
/// \arg off Offset applied to reach successive items. Default is 0,
|
/// \arg off Offset applied to reach successive items. Default is 0,
|
||||||
/// which creates an effectively constant iterator.
|
/// which creates an effectively constant iterator.
|
||||||
|
const_offset_iterator(T const *t, size_t off=0) : m_t(t), m_off(off) {}
|
||||||
|
|
||||||
|
const T * operator++() { m_t = offset_ptr<T>(m_t, m_off); return m_t; }
|
||||||
|
const T * operator++(int) { T* tmp = m_t; operator++(); return tmp; }
|
||||||
|
|
||||||
|
bool operator==(T* p) const { return p == m_t; }
|
||||||
|
bool operator!=(T* p) const { return p != m_t; }
|
||||||
|
bool operator==(const_offset_iterator<T> &i) const { return i.m_t == m_t; }
|
||||||
|
bool operator!=(const_offset_iterator<T> &i) const { return i.m_t != m_t; }
|
||||||
|
|
||||||
|
const T& operator*() const { return *m_t; }
|
||||||
|
operator const T& () const { return *m_t; }
|
||||||
|
const T* operator->() const { return m_t; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
T const *m_t;
|
||||||
|
size_t m_off;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// iterator for an array of `const T` whose size is known at runtime
|
||||||
|
/// \tparam T type of the objects in the array, whose size might not be
|
||||||
|
/// what is returned by sizeof(T).
|
||||||
|
template <typename T>
|
||||||
|
class offset_iterator
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// constructor.
|
||||||
|
/// \arg t pointer to the first item in the array
|
||||||
|
/// \arg off offset applied to reach successive items. default is 0,
|
||||||
|
/// which creates an effectively constant iterator.
|
||||||
offset_iterator(T *t, size_t off=0) : m_t(t), m_off(off) {}
|
offset_iterator(T *t, size_t off=0) : m_t(t), m_off(off) {}
|
||||||
|
|
||||||
T * operator++() { m_t = offset_ptr<T>(m_t, m_off); return m_t; }
|
T * operator++() { m_t = offset_ptr<T>(m_t, m_off); return m_t; }
|
||||||
T * operator++(int) { T* tmp = m_t; operator++(); return tmp; }
|
T * operator++(int) { T* tmp = m_t; operator++(); return tmp; }
|
||||||
|
|
||||||
bool operator==(T* p) { return p == m_t; }
|
bool operator==(T *p) const { return p == m_t; }
|
||||||
bool operator!=(T* p) { return p != m_t; }
|
bool operator!=(T *p) const { return p != m_t; }
|
||||||
bool operator==(offset_iterator<T> &i) { return i.m_t == m_t; }
|
bool operator==(offset_iterator<T> &i) const { return i.m_t == m_t; }
|
||||||
bool operator!=(offset_iterator<T> &i) { return i.m_t != m_t; }
|
bool operator!=(offset_iterator<T> &i) const { return i.m_t != m_t; }
|
||||||
|
|
||||||
T & operator*() const { return *m_t; }
|
T & operator*() const { return *m_t; }
|
||||||
operator T & () const { return *m_t; }
|
operator T & () const { return *m_t; }
|
||||||
@@ -39,3 +69,4 @@ private:
|
|||||||
T *m_t;
|
T *m_t;
|
||||||
size_t m_off;
|
size_t m_off;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,50 +0,0 @@
|
|||||||
#include "elf/elf.h"
|
|
||||||
#include "elf/headers.h"
|
|
||||||
#include "kutil/memory.h"
|
|
||||||
|
|
||||||
static const uint32_t expected_magic = 0x464c457f; // "\x7f" "ELF"
|
|
||||||
|
|
||||||
namespace elf {
|
|
||||||
|
|
||||||
elf::elf(const void *data, size_t size) :
|
|
||||||
m_data(data),
|
|
||||||
m_size(size)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
bool
|
|
||||||
elf::valid() const
|
|
||||||
{
|
|
||||||
const file_header *fheader = header();
|
|
||||||
|
|
||||||
return
|
|
||||||
fheader->magic == expected_magic &&
|
|
||||||
fheader->word_size == wordsize::bits64 &&
|
|
||||||
fheader->endianness == encoding::lsb &&
|
|
||||||
fheader->os_abi == osabi::sysV &&
|
|
||||||
fheader->file_type == filetype::executable &&
|
|
||||||
fheader->machine_type == machine::x64 &&
|
|
||||||
fheader->ident_version == 1 &&
|
|
||||||
fheader->version == 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const program_header *
|
|
||||||
elf::program(unsigned index) const
|
|
||||||
{
|
|
||||||
const file_header *fheader = header();
|
|
||||||
uint64_t off = fheader->ph_offset + (index * fheader->ph_entsize);
|
|
||||||
const void *pheader = kutil::offset_pointer(m_data, off);
|
|
||||||
return reinterpret_cast<const program_header *>(pheader);
|
|
||||||
}
|
|
||||||
|
|
||||||
const section_header *
|
|
||||||
elf::section(unsigned index) const
|
|
||||||
{
|
|
||||||
const file_header *fheader = header();
|
|
||||||
uint64_t off = fheader->sh_offset + (index * fheader->sh_entsize);
|
|
||||||
const void *sheader = kutil::offset_pointer(m_data, off);
|
|
||||||
return reinterpret_cast<const section_header *>(sheader);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
} // namespace elf
|
|
||||||
45
src/libraries/elf/file.cpp
Normal file
45
src/libraries/elf/file.cpp
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
#include "elf/file.h"
|
||||||
|
#include "elf/headers.h"
|
||||||
|
#include "pointer_manipulation.h"
|
||||||
|
|
||||||
|
static const uint32_t expected_magic = 0x464c457f; // "\x7f" "ELF"
|
||||||
|
|
||||||
|
namespace elf {
|
||||||
|
|
||||||
|
inline const file_header * fh(const void *data) { return reinterpret_cast<const file_header*>(data); }
|
||||||
|
|
||||||
|
file::file(const void *data, size_t size) :
|
||||||
|
m_programs(offset_ptr<program_header>(data, fh(data)->ph_offset), fh(data)->ph_entsize, fh(data)->ph_num),
|
||||||
|
m_sections(offset_ptr<section_header>(data, fh(data)->sh_offset), fh(data)->sh_entsize, fh(data)->sh_num),
|
||||||
|
m_data(data),
|
||||||
|
m_size(size)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
file::valid() const
|
||||||
|
{
|
||||||
|
if (m_size < sizeof(file_header))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
const file_header *fheader = header();
|
||||||
|
|
||||||
|
return
|
||||||
|
fheader->magic == expected_magic &&
|
||||||
|
fheader->word_size == wordsize::bits64 &&
|
||||||
|
fheader->endianness == encoding::lsb &&
|
||||||
|
fheader->os_abi == osabi::sysV &&
|
||||||
|
fheader->file_type == filetype::executable &&
|
||||||
|
fheader->machine_type == machine::x64 &&
|
||||||
|
fheader->ident_version == 1 &&
|
||||||
|
fheader->version == 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
uintptr_t
|
||||||
|
file::entrypoint() const
|
||||||
|
{
|
||||||
|
return static_cast<uintptr_t>(header()->entrypoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
} // namespace elf
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
#pragma once
|
|
||||||
#include <stddef.h>
|
|
||||||
#include <stdint.h>
|
|
||||||
#include "elf/headers.h"
|
|
||||||
|
|
||||||
namespace elf {
|
|
||||||
|
|
||||||
class elf
|
|
||||||
{
|
|
||||||
public:
|
|
||||||
/// Constructor: Create an elf object out of ELF data in memory
|
|
||||||
/// \arg data The ELF data to read
|
|
||||||
/// \arg size Size of the ELF data, in bytes
|
|
||||||
elf(const void *data, size_t size);
|
|
||||||
|
|
||||||
/// Check the validity of the ELF data
|
|
||||||
/// \returns true for valid ELF data
|
|
||||||
bool valid() const;
|
|
||||||
|
|
||||||
/// Get the entrypoint address of the program image
|
|
||||||
/// \returns A pointer to the entrypoint of the program
|
|
||||||
inline uintptr_t entrypoint() const
|
|
||||||
{
|
|
||||||
return static_cast<uintptr_t>(header()->entrypoint);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the number of program sections in the image
|
|
||||||
/// \returns The number of program section entries
|
|
||||||
inline unsigned program_count() const
|
|
||||||
{
|
|
||||||
return header()->ph_num;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a program header
|
|
||||||
/// \arg index The index number of the program header
|
|
||||||
/// \returns A pointer to the program header data
|
|
||||||
const program_header * program(unsigned index) const;
|
|
||||||
|
|
||||||
/// Get the number of data sections in the image
|
|
||||||
/// \returns The number of section entries
|
|
||||||
inline unsigned section_count() const
|
|
||||||
{
|
|
||||||
return header()->sh_num;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get a section header
|
|
||||||
/// \arg index The index number of the section header
|
|
||||||
/// \returns A pointer to the section header data
|
|
||||||
const section_header * section(unsigned index) const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
inline const file_header *header() const
|
|
||||||
{
|
|
||||||
return reinterpret_cast<const file_header *>(m_data);
|
|
||||||
}
|
|
||||||
|
|
||||||
const void *m_data;
|
|
||||||
size_t m_size;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
75
src/libraries/elf/include/elf/file.h
Normal file
75
src/libraries/elf/include/elf/file.h
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
#pragma once
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
#include "pointer_manipulation.h"
|
||||||
|
|
||||||
|
namespace elf {
|
||||||
|
|
||||||
|
struct file_header;
|
||||||
|
struct program_header;
|
||||||
|
struct section_header;
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
class subheaders
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using iterator = const_offset_iterator<T>;
|
||||||
|
|
||||||
|
subheaders(const T *start, size_t size, unsigned count) :
|
||||||
|
m_start(start), m_size(size), m_count(count) {}
|
||||||
|
|
||||||
|
inline size_t size() const { return m_size; }
|
||||||
|
inline unsigned count() const { return m_count; }
|
||||||
|
|
||||||
|
inline const T & operator [] (int i) const { return *offset_ptr<T>(m_start, m_size*i); }
|
||||||
|
inline const iterator begin() const { return iterator(m_start, m_size); }
|
||||||
|
inline const iterator end() const { return offset_ptr<T>(m_start, m_size*m_count); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
const T *m_start;
|
||||||
|
size_t m_size;
|
||||||
|
unsigned m_count;
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Represents a full ELF file's data
|
||||||
|
class file
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
/// Constructor: Create an elf object out of ELF data in memory
|
||||||
|
/// \arg data The ELF data to read
|
||||||
|
/// \arg size Size of the ELF data, in bytes
|
||||||
|
file(const void *data, size_t size);
|
||||||
|
|
||||||
|
/// Check the validity of the ELF data
|
||||||
|
/// \returns true for valid ELF data
|
||||||
|
bool valid() const;
|
||||||
|
|
||||||
|
/// Get the entrypoint address of the program image
|
||||||
|
/// \returns A pointer to the entrypoint of the program
|
||||||
|
uintptr_t entrypoint() const;
|
||||||
|
|
||||||
|
/// Get the base address of the program in memory
|
||||||
|
inline uintptr_t base() const {
|
||||||
|
return reinterpret_cast<uintptr_t>(m_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the ELF program headers
|
||||||
|
inline const subheaders<program_header> & programs() const { return m_programs; }
|
||||||
|
|
||||||
|
/// Get the ELF section headers
|
||||||
|
inline const subheaders<section_header> & sections() const { return m_sections; }
|
||||||
|
|
||||||
|
inline const file_header * header() const {
|
||||||
|
return reinterpret_cast<const file_header *>(m_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
subheaders<program_header> m_programs;
|
||||||
|
subheaders<section_header> m_sections;
|
||||||
|
|
||||||
|
const void *m_data;
|
||||||
|
size_t m_size;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
6
src/libraries/elf/module.toml
Normal file
6
src/libraries/elf/module.toml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
name = "elf"
|
||||||
|
kind = "lib"
|
||||||
|
includes = [ "src/libraries/elf/include" ]
|
||||||
|
sources = [
|
||||||
|
"file.cpp",
|
||||||
|
]
|
||||||
Reference in New Issue
Block a user