mirror of
https://github.com/Noratrieb/elven-forest.git
synced 2026-01-14 10:45:03 +01:00
uwu
This commit is contained in:
parent
98eaa92612
commit
84f4ab4297
4 changed files with 207 additions and 17 deletions
|
|
@ -2,8 +2,8 @@ use std::{fmt::Display, fs::File};
|
||||||
|
|
||||||
use anyhow::Context;
|
use anyhow::Context;
|
||||||
use elven_parser::{
|
use elven_parser::{
|
||||||
consts::{ShType, RX86_64},
|
consts::{DynamicTag, ShType, RX86_64},
|
||||||
defs::Elf,
|
defs::{Addr, Elf},
|
||||||
ElfParseError,
|
ElfParseError,
|
||||||
};
|
};
|
||||||
use memmap2::Mmap;
|
use memmap2::Mmap;
|
||||||
|
|
@ -40,6 +40,12 @@ struct RelaTable {
|
||||||
addend: u64,
|
addend: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Tabled)]
|
||||||
|
struct DynTable {
|
||||||
|
tag: DynamicTag,
|
||||||
|
value: Addr,
|
||||||
|
}
|
||||||
|
|
||||||
fn print_file(path: &str) -> anyhow::Result<()> {
|
fn print_file(path: &str) -> anyhow::Result<()> {
|
||||||
println!("{path}");
|
println!("{path}");
|
||||||
|
|
||||||
|
|
@ -59,7 +65,7 @@ fn print_file(path: &str) -> anyhow::Result<()> {
|
||||||
HeaderTable("osabi", &ident.osabi),
|
HeaderTable("osabi", &ident.osabi),
|
||||||
HeaderTable("type", &header.r#type),
|
HeaderTable("type", &header.r#type),
|
||||||
HeaderTable("machine", &header.machine),
|
HeaderTable("machine", &header.machine),
|
||||||
HeaderTable("entrypoint (hex)", &header.entry),
|
HeaderTable("entrypoint", &header.entry),
|
||||||
];
|
];
|
||||||
|
|
||||||
let mut table = Table::new(header_tab);
|
let mut table = Table::new(header_tab);
|
||||||
|
|
@ -90,8 +96,10 @@ fn print_file(path: &str) -> anyhow::Result<()> {
|
||||||
.relas()?
|
.relas()?
|
||||||
.map(|(sh, rela)| {
|
.map(|(sh, rela)| {
|
||||||
let section = elf.sh_string(sh.name)?.to_string();
|
let section = elf.sh_string(sh.name)?.to_string();
|
||||||
|
|
||||||
let sym = elf.symbol(rela.info.sym())?;
|
let sym = elf.symbol(rela.info.sym())?;
|
||||||
let symbol = elf.string(sym.name)?.to_string();
|
let symbol = elf.string(sym.name)?.to_string();
|
||||||
|
|
||||||
let offset = rela.offset.0;
|
let offset = rela.offset.0;
|
||||||
let r#type = elven_parser::consts::RX86_64(rela.info.r#type());
|
let r#type = elven_parser::consts::RX86_64(rela.info.r#type());
|
||||||
let addend = rela.addend;
|
let addend = rela.addend;
|
||||||
|
|
@ -108,6 +116,16 @@ fn print_file(path: &str) -> anyhow::Result<()> {
|
||||||
|
|
||||||
print_table(Table::new(relas));
|
print_table(Table::new(relas));
|
||||||
|
|
||||||
|
if let Ok(dyns) = elf.dyn_entries() {
|
||||||
|
println!("\nDynamic entries");
|
||||||
|
|
||||||
|
let dyns = dyns.iter().map(|dy| DynTable {
|
||||||
|
tag: dy.tag,
|
||||||
|
value: Addr(dy.val),
|
||||||
|
});
|
||||||
|
print_table(Table::new(dyns));
|
||||||
|
}
|
||||||
|
|
||||||
println!();
|
println!();
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -129,8 +129,12 @@ pub const ET_EXEC: u16 = 2;
|
||||||
pub const ET_DYN: u16 = 3;
|
pub const ET_DYN: u16 = 3;
|
||||||
pub const ET_CORE: u16 = 4;
|
pub const ET_CORE: u16 = 4;
|
||||||
|
|
||||||
pub const EM_NONE: u16 = 0; /* No machine */
|
const_group_with_fmt! {
|
||||||
pub const EM_X86_64: u16 = 62; /* AMD x86-64 architecture */
|
pub struct Machine(u16): "Machine"
|
||||||
|
|
||||||
|
pub const EM_NONE = 0; /* No machine */
|
||||||
|
pub const EM_X86_64 = 62; /* AMD x86-64 architecture */
|
||||||
|
}
|
||||||
|
|
||||||
pub const EV_NONE: u32 = 0;
|
pub const EV_NONE: u32 = 0;
|
||||||
|
|
||||||
|
|
@ -297,6 +301,105 @@ const_group_with_fmt! {
|
||||||
pub const R_X86_64_NUM = 43;
|
pub const R_X86_64_NUM = 43;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ------------------
|
||||||
|
// Dynamic
|
||||||
|
// ------------------
|
||||||
|
|
||||||
|
const_group_with_fmt! {
|
||||||
|
pub struct DynamicTag(u64): "DT"
|
||||||
|
|
||||||
|
pub const DT_NULL = 0; /* Marks end of dynamic section */
|
||||||
|
pub const DT_NEEDED = 1; /* Name of needed library */
|
||||||
|
pub const DT_PLTRELSZ = 2; /* Size in bytes of PLT relocs */
|
||||||
|
pub const DT_PLTGOT = 3; /* Processor defined value */
|
||||||
|
pub const DT_HASH = 4; /* Address of symbol hash table */
|
||||||
|
pub const DT_STRTAB = 5; /* Address of string table */
|
||||||
|
pub const DT_SYMTAB = 6; /* Address of symbol table */
|
||||||
|
pub const DT_RELA = 7; /* Address of Rela relocs */
|
||||||
|
pub const DT_RELASZ = 8; /* Total size of Rela relocs */
|
||||||
|
pub const DT_RELAENT = 9; /* Size of one Rela reloc */
|
||||||
|
pub const DT_STRSZ = 10; /* Size of string table */
|
||||||
|
pub const DT_SYMENT = 11; /* Size of one symbol table entry */
|
||||||
|
pub const DT_INIT = 12; /* Address of init function */
|
||||||
|
pub const DT_FINI = 13; /* Address of termination function */
|
||||||
|
pub const DT_SONAME = 14; /* Name of shared object */
|
||||||
|
pub const DT_RPATH = 15; /* Library search path (deprecated) */
|
||||||
|
pub const DT_SYMBOLIC = 16; /* Start symbol search here */
|
||||||
|
pub const DT_REL = 17; /* Address of Rel relocs */
|
||||||
|
pub const DT_RELSZ = 18; /* Total size of Rel relocs */
|
||||||
|
pub const DT_RELENT = 19; /* Size of one Rel reloc */
|
||||||
|
pub const DT_PLTREL = 20; /* Type of reloc in PLT */
|
||||||
|
pub const DT_DEBUG = 21; /* For debugging; unspecified */
|
||||||
|
pub const DT_TEXTREL = 22; /* Reloc might modify .text */
|
||||||
|
pub const DT_JMPREL = 23; /* Address of PLT relocs */
|
||||||
|
pub const DT_BIND_NOW = 24; /* Process relocations of object */
|
||||||
|
pub const DT_INIT_ARRAY = 25; /* Array with addresses of init fct */
|
||||||
|
pub const DT_FINI_ARRAY = 26; /* Array with addresses of fini fct */
|
||||||
|
pub const DT_INIT_ARRAYSZ = 27; /* Size in bytes of DT_INIT_ARRAY */
|
||||||
|
pub const DT_FINI_ARRAYSZ = 28; /* Size in bytes of DT_FINI_ARRAY */
|
||||||
|
pub const DT_RUNPATH = 29; /* Library search path */
|
||||||
|
pub const DT_FLAGS = 30; /* Flags for the object being loaded */
|
||||||
|
pub const DT_PREINIT_ARRAY = 32; /* Array with addresses of preinit fct*/
|
||||||
|
pub const DT_PREINIT_ARRAYSZ = 33; /* size in bytes of DT_PREINIT_ARRAY */
|
||||||
|
pub const DT_SYMTAB_SHNDX = 34; /* Address of SYMTAB_SHNDX section */
|
||||||
|
pub const DT_NUM = 35; /* Number used */
|
||||||
|
pub const DT_PROCNUM = 0x37; /* Most used by any processor */
|
||||||
|
|
||||||
|
/* DT_* entries which fall between DT_VALRNGHI & DT_VALRNGLO use the
|
||||||
|
Dyn.d_un.d_val field of the Elf*_Dyn structure. This follows Sun's
|
||||||
|
approach. */
|
||||||
|
pub const DT_GNU_PRELINKED = 0x6ffffdf5; /* Prelinking timestamp */
|
||||||
|
pub const DT_GNU_CONFLICTSZ = 0x6ffffdf6; /* Size of conflict section */
|
||||||
|
pub const DT_GNU_LIBLISTSZ = 0x6ffffdf7; /* Size of library list */
|
||||||
|
pub const DT_CHECKSUM = 0x6ffffdf8;
|
||||||
|
pub const DT_PLTPADSZ = 0x6ffffdf9;
|
||||||
|
pub const DT_MOVEENT = 0x6ffffdfa;
|
||||||
|
pub const DT_MOVESZ = 0x6ffffdfb;
|
||||||
|
pub const DT_FEATURE_1 = 0x6ffffdfc; /* Feature selection (DTF_*). */
|
||||||
|
pub const DT_POSFLAG_1 = 0x6ffffdfd; /* Flags for DT_* entries, effecting the: u following DT_* entry. */
|
||||||
|
pub const DT_SYMINSZ = 0x6ffffdfe; /* Size of syminfo table (in bytes) */
|
||||||
|
pub const DT_SYMINENT = 0x6ffffdff; /* Entry size of syminfo */
|
||||||
|
|
||||||
|
/* DT_* entries which fall between DT_ADDRRNGHI & DT_ADDRRNGLO use the
|
||||||
|
Dyn.d_un.d_ptr field of the Elf*_Dyn structure.
|
||||||
|
|
||||||
|
If any adjustment is made to the ELF object after it has been
|
||||||
|
built these entries will need to be adjusted. */
|
||||||
|
pub const DT_GNU_HASH = 0x6ffffef5; /* GNU-style hash table. */
|
||||||
|
pub const DT_TLSDESC_PLT = 0x6ffffef6;
|
||||||
|
pub const DT_TLSDESC_GOT = 0x6ffffef7;
|
||||||
|
pub const DT_GNU_CONFLICT = 0x6ffffef8; /* Start of conflict section */
|
||||||
|
pub const DT_GNU_LIBLIST = 0x6ffffef9; /* Library list */
|
||||||
|
pub const DT_CONFIG = 0x6ffffefa; /* Configuration information. */
|
||||||
|
pub const DT_DEPAUDIT = 0x6ffffefb; /* Dependency auditing. */
|
||||||
|
pub const DT_AUDIT = 0x6ffffefc; /* Object auditing. */
|
||||||
|
pub const DT_PLTPAD = 0x6ffffefd; /* PLT padding. */
|
||||||
|
pub const DT_MOVETAB = 0x6ffffefe; /* Move table. */
|
||||||
|
pub const DT_SYMINFO = 0x6ffffeff; /* Syminfo table. */
|
||||||
|
|
||||||
|
/* The versioning entry types. The next are defined as part of the
|
||||||
|
GNU extension. */
|
||||||
|
pub const DT_VERSYM = 0x6ffffff0;
|
||||||
|
pub const DT_RELACOUNT = 0x6ffffff9;
|
||||||
|
pub const DT_RELCOUNT = 0x6ffffffa;
|
||||||
|
/* These were chosen by Sun. */
|
||||||
|
pub const DT_FLAGS_1 = 0x6ffffffb; /* State flags, see DF_1_* below. */
|
||||||
|
pub const DT_VERDEF = 0x6ffffffc; /* Address of version definition table */
|
||||||
|
pub const DT_VERDEFNUM = 0x6ffffffd; /* Number of version definitions */
|
||||||
|
pub const DT_VERNEED = 0x6ffffffe; /* Address of table with needed versions */
|
||||||
|
pub const DT_VERNEEDNUM = 0x6fffffff; /* Number of needed versions */
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const DT_ENCODING: u64 = 32; /* Start of encoded range */
|
||||||
|
pub const DT_LOOS: u64 = 0x6000000d; /* Start of OS-specific */
|
||||||
|
pub const DT_HIOS: u64 = 0x6ffff000; /* End of OS-specific */
|
||||||
|
pub const DT_LOPROC: u64 = 0x70000000; /* Start of processor-specific */
|
||||||
|
pub const DT_HIPROC: u64 = 0x7fffffff; /* End of processor-specific */
|
||||||
|
pub const DT_ADDRRNGLO: u64 = 0x6ffffe00;
|
||||||
|
pub const DT_ADDRRNGHI: u64 = 0x6ffffeff;
|
||||||
|
pub const DT_VALRNGLO: u64 = 0x6ffffd00;
|
||||||
|
pub const DT_VALRNGHI: u64 = 0x6ffffdff;
|
||||||
|
|
||||||
impl SectionIdx {
|
impl SectionIdx {
|
||||||
pub fn usize(self) -> usize {
|
pub fn usize(self) -> usize {
|
||||||
self.0 as usize
|
self.0 as usize
|
||||||
|
|
|
||||||
|
|
@ -3,13 +3,16 @@
|
||||||
//! See https://man7.org/linux/man-pages/man5/elf.5.html
|
//! See https://man7.org/linux/man-pages/man5/elf.5.html
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
consts as c,
|
consts::{self as c, DynamicTag, ShType},
|
||||||
idx::{define_idx, ElfIndexExt, ToIdxUsize},
|
idx::{define_idx, ElfIndexExt, ToIdxUsize},
|
||||||
ElfParseError, Result,
|
ElfParseError, Result,
|
||||||
};
|
};
|
||||||
use bstr::BStr;
|
use bstr::BStr;
|
||||||
|
|
||||||
use std::{fmt::{Debug, Display}, mem, string};
|
use std::{
|
||||||
|
fmt::{Debug, Display},
|
||||||
|
mem, string,
|
||||||
|
};
|
||||||
|
|
||||||
use bytemuck::{Pod, PodCastError, Zeroable};
|
use bytemuck::{Pod, PodCastError, Zeroable};
|
||||||
|
|
||||||
|
|
@ -20,13 +23,13 @@ pub struct Addr(pub u64);
|
||||||
|
|
||||||
impl Debug for Addr {
|
impl Debug for Addr {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{:x}", self.0)
|
write!(f, "0x{:x}", self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for Addr {
|
impl Display for Addr {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
write!(f, "{:x}", self.0)
|
write!(f, "0x{:x}", self.0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -63,7 +66,7 @@ pub struct Elf<'a> {
|
||||||
pub struct ElfHeader {
|
pub struct ElfHeader {
|
||||||
pub ident: ElfIdent,
|
pub ident: ElfIdent,
|
||||||
pub r#type: u16,
|
pub r#type: u16,
|
||||||
pub machine: u16,
|
pub machine: c::Machine,
|
||||||
pub version: u32,
|
pub version: u32,
|
||||||
pub entry: Addr,
|
pub entry: Addr,
|
||||||
pub phoff: Offset,
|
pub phoff: Offset,
|
||||||
|
|
@ -185,6 +188,13 @@ impl Debug for RelInfo {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, Zeroable, Pod)]
|
||||||
|
#[repr(C)]
|
||||||
|
pub struct Dyn {
|
||||||
|
pub tag: c::DynamicTag,
|
||||||
|
pub val: u64,
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> Elf<'a> {
|
impl<'a> Elf<'a> {
|
||||||
pub fn new(data: &'a [u8]) -> Result<Self> {
|
pub fn new(data: &'a [u8]) -> Result<Self> {
|
||||||
let magic = data[..c::SELFMAG].try_into().unwrap();
|
let magic = data[..c::SELFMAG].try_into().unwrap();
|
||||||
|
|
@ -263,6 +273,13 @@ impl<'a> Elf<'a> {
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn section_header_by_type(&self, ty: u32) -> Result<&Shdr> {
|
||||||
|
self.section_headers()?
|
||||||
|
.iter()
|
||||||
|
.find(|sh| sh.r#type == ty)
|
||||||
|
.ok_or(ElfParseError::SectionTypeNotFound(ShType(ty)))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn section_content(&self, sh: &Shdr) -> Result<&[u8]> {
|
pub fn section_content(&self, sh: &Shdr) -> Result<&[u8]> {
|
||||||
if sh.r#type.0 == c::SHT_NOBITS {
|
if sh.r#type.0 == c::SHT_NOBITS {
|
||||||
return Ok(&[]);
|
return Ok(&[]);
|
||||||
|
|
@ -320,6 +337,23 @@ impl<'a> Elf<'a> {
|
||||||
Ok(BStr::new(&indexed[..end]))
|
Ok(BStr::new(&indexed[..end]))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn dyn_string(&self, idx: StringIdx) -> Result<&BStr> {
|
||||||
|
let tab_addr = self.dyn_entry_by_tag(c::DT_STRTAB)?;
|
||||||
|
let tab_sz = self.dyn_entry_by_tag(c::DT_STRSZ)?;
|
||||||
|
|
||||||
|
let str_table = self
|
||||||
|
.data
|
||||||
|
.get_elf(tab_addr.val.., "dyn string table")?
|
||||||
|
.get_elf(..tab_sz.val, "dyn string table size")?;
|
||||||
|
|
||||||
|
let indexed = str_table.get_elf(idx.., "string offset")?;
|
||||||
|
let end = indexed
|
||||||
|
.iter()
|
||||||
|
.position(|&c| c == b'\0')
|
||||||
|
.ok_or(ElfParseError::NoStringNulTerm(idx.to_idx_usize()))?;
|
||||||
|
Ok(BStr::new(&indexed[..end]))
|
||||||
|
}
|
||||||
|
|
||||||
pub fn relas(&self) -> Result<impl Iterator<Item = (&Shdr, &Rela)>> {
|
pub fn relas(&self) -> Result<impl Iterator<Item = (&Shdr, &Rela)>> {
|
||||||
Ok(self
|
Ok(self
|
||||||
.section_headers()?
|
.section_headers()?
|
||||||
|
|
@ -336,11 +370,7 @@ impl<'a> Elf<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn symbols(&self) -> Result<&[Sym]> {
|
pub fn symbols(&self) -> Result<&[Sym]> {
|
||||||
let sh = self
|
let sh = self.section_header_by_type(c::SHT_SYMTAB)?;
|
||||||
.section_headers()?
|
|
||||||
.iter()
|
|
||||||
.find(|sh| sh.r#type == c::SHT_SYMTAB)
|
|
||||||
.ok_or(ElfParseError::SymtabNotFound)?;
|
|
||||||
|
|
||||||
let data = self.section_content(sh)?;
|
let data = self.section_content(sh)?;
|
||||||
|
|
||||||
|
|
@ -350,6 +380,41 @@ impl<'a> Elf<'a> {
|
||||||
pub fn symbol(&self, idx: SymIdx) -> Result<&Sym> {
|
pub fn symbol(&self, idx: SymIdx) -> Result<&Sym> {
|
||||||
self.symbols()?.get_elf(idx, "symbol index")
|
self.symbols()?.get_elf(idx, "symbol index")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn dyn_symbols(&self) -> Result<&[Sym]> {
|
||||||
|
let addr = self.dyn_entry_by_tag(c::DT_SYMTAB)?;
|
||||||
|
let size = self.dyn_entry_by_tag(c::DT_SYMENT)?;
|
||||||
|
|
||||||
|
dbg!(addr, size);
|
||||||
|
|
||||||
|
let data = self.dyn_content(addr.val, size.val)?;
|
||||||
|
|
||||||
|
load_slice(data, data.len() / mem::size_of::<Sym>())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dyn_symbol(&self, idx: SymIdx) -> Result<&Sym> {
|
||||||
|
dbg!(self.dyn_symbols()?).get_elf(idx, "symbol index")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dyn_entries(&self) -> Result<&[Dyn]> {
|
||||||
|
let sh = self.section_header_by_name(b".dynamic")?;
|
||||||
|
let data = self.section_content(sh)?;
|
||||||
|
|
||||||
|
load_slice(data, data.len() / mem::size_of::<Dyn>())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dyn_entry_by_tag(&self, tag: u64) -> Result<&Dyn> {
|
||||||
|
self.dyn_entries()?
|
||||||
|
.iter()
|
||||||
|
.find(|dy| dy.tag == tag)
|
||||||
|
.ok_or(ElfParseError::DynEntryNotFound(DynamicTag(tag)))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn dyn_content(&self, addr: u64, size: u64) -> Result<&[u8]> {
|
||||||
|
self.data
|
||||||
|
.get_elf(addr.., "dyn content offset")?
|
||||||
|
.get_elf(..size, "section size")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn load_ref<T: Pod>(data: &[u8]) -> Result<&T> {
|
fn load_ref<T: Pod>(data: &[u8]) -> Result<&T> {
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
use consts::{DynamicTag, ShType};
|
||||||
|
|
||||||
pub mod consts;
|
pub mod consts;
|
||||||
pub mod defs;
|
pub mod defs;
|
||||||
mod idx;
|
mod idx;
|
||||||
|
|
@ -20,10 +22,12 @@ pub enum ElfParseError {
|
||||||
IndexOutOfBounds(&'static str, usize),
|
IndexOutOfBounds(&'static str, usize),
|
||||||
#[error("String in string table does not end with a nul terminator: String offset: {0}")]
|
#[error("String in string table does not end with a nul terminator: String offset: {0}")]
|
||||||
NoStringNulTerm(usize),
|
NoStringNulTerm(usize),
|
||||||
#[error("The SHT_SYMTAB section was not found")]
|
#[error("The {0} section was not found")]
|
||||||
SymtabNotFound,
|
SectionTypeNotFound(ShType),
|
||||||
#[error("The section with the name {0:?} was not found")]
|
#[error("The section with the name {0:?} was not found")]
|
||||||
SectionNotFound(std::result::Result<String, Vec<u8>>),
|
SectionNotFound(std::result::Result<String, Vec<u8>>),
|
||||||
|
#[error("Dynamic entry not found: {0}")]
|
||||||
|
DynEntryNotFound(DynamicTag),
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, ElfParseError>;
|
pub type Result<T> = std::result::Result<T, ElfParseError>;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue