Add initial support to loongarch 32/64 (#4810)
This commit is contained in:
parent
cf6da4ff2b
commit
b019d9d4ca
10 changed files with 2870 additions and 5 deletions
22
librz/arch/isa/loongarch/loongarch.h
Normal file
22
librz/arch/isa/loongarch/loongarch.h
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
// SPDX-FileCopyrightText: 2024 deroad <deroad@kumo.xn--q9jyb4c>
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
#include <rz_types.h>
|
||||
#include <capstone.h>
|
||||
|
||||
#ifndef RZ_LOONGARCH_H
|
||||
#define RZ_LOONGARCH_H
|
||||
|
||||
#include <capstone/capstone.h>
|
||||
#include <capstone/loongarch.h>
|
||||
|
||||
typedef struct {
|
||||
csh h;
|
||||
cs_mode mode;
|
||||
cs_insn *insn;
|
||||
ut32 count;
|
||||
ut32 word;
|
||||
RzPVector /*<RzAsmTokenPattern *>*/ *token_patterns;
|
||||
} RzAsmLoongArchContext;
|
||||
|
||||
#endif // RZ_LOONGARCH_H
|
||||
88
librz/arch/isa/loongarch/loongarch.inc
Normal file
88
librz/arch/isa/loongarch/loongarch.inc
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
// SPDX-FileCopyrightText: 2024 deroad <deroad@kumo.xn--q9jyb4c>
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
#include <capstone/capstone.h>
|
||||
#include <capstone/loongarch.h>
|
||||
#include "loongarch.h"
|
||||
|
||||
#define LOONGARCH_CPUS "loongarch32,loongarch64"
|
||||
|
||||
static inline bool
|
||||
loongarch_setup_cs_handle(RzAsmLoongArchContext *ctx, bool is_64_bits, bool big_endian) {
|
||||
cs_mode mode = big_endian ? CS_MODE_BIG_ENDIAN : CS_MODE_LITTLE_ENDIAN;
|
||||
if (mode != ctx->mode) {
|
||||
cs_close(&ctx->h);
|
||||
ctx->h = 0;
|
||||
ctx->mode = mode;
|
||||
}
|
||||
|
||||
if (is_64_bits) {
|
||||
mode |= CS_MODE_LOONGARCH64;
|
||||
} else {
|
||||
mode |= CS_MODE_LOONGARCH32;
|
||||
}
|
||||
|
||||
if (ctx->h != 0) {
|
||||
return true;
|
||||
}
|
||||
cs_err err = cs_open(CS_ARCH_LOONGARCH, mode, &ctx->h);
|
||||
if (err) {
|
||||
const char *error_str = cs_strerror(err);
|
||||
RZ_LOG_ERROR("Failed on cs_open() with error returned: %u: %s\n", err, error_str);
|
||||
return false;
|
||||
}
|
||||
err = cs_option(ctx->h, CS_OPT_DETAIL, CS_OPT_ON);
|
||||
if (err) {
|
||||
const char *error_str = cs_strerror(err);
|
||||
RZ_LOG_ERROR("Failed on cs_option() with error returned: %u: %s\n", err, error_str);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline ut8 loongarch_op_count(cs_insn *insn) {
|
||||
return insn->detail->loongarch.op_count;
|
||||
}
|
||||
|
||||
static inline cs_loongarch_op *loongarch_op_get(cs_insn *insn, int idx) {
|
||||
if (idx >= loongarch_op_count(insn)) {
|
||||
RZ_LOG_WARN("Failed to get operand%d [%d]: \"%s %s\"\n",
|
||||
idx, loongarch_op_count(insn), insn->mnemonic, insn->op_str);
|
||||
rz_warn_if_reached();
|
||||
return NULL;
|
||||
}
|
||||
return &insn->detail->loongarch.operands[idx];
|
||||
}
|
||||
|
||||
static inline bool loongarch_op_is_reg(RzAsmLoongArchContext *ctx, int idx, loongarch_reg reg_no) {
|
||||
const cs_loongarch_op *op = loongarch_op_get(ctx->insn, idx);
|
||||
if (!op || op->type != LOONGARCH_OP_REG) {
|
||||
RZ_LOG_WARN("Failed to get operand%d [%d]: \"%s %s\" [reg]\n",
|
||||
idx, loongarch_op_count(ctx->insn), ctx->insn->mnemonic, ctx->insn->op_str);
|
||||
rz_warn_if_reached();
|
||||
return NULL;
|
||||
}
|
||||
return op->reg == reg_no;
|
||||
}
|
||||
|
||||
static inline const char *loongarch_op_as_reg(RzAsmLoongArchContext *ctx, int idx) {
|
||||
const cs_loongarch_op *op = loongarch_op_get(ctx->insn, idx);
|
||||
if (!op || op->type != LOONGARCH_OP_REG) {
|
||||
RZ_LOG_WARN("Failed to get operand%d [%d]: \"%s %s\" [reg]\n",
|
||||
idx, loongarch_op_count(ctx->insn), ctx->insn->mnemonic, ctx->insn->op_str);
|
||||
rz_warn_if_reached();
|
||||
return NULL;
|
||||
}
|
||||
return cs_reg_name(ctx->h, op->reg);
|
||||
}
|
||||
|
||||
static inline st64 loongarch_op_as_imm(RzAsmLoongArchContext *ctx, int idx) {
|
||||
const cs_loongarch_op *op = loongarch_op_get(ctx->insn, idx);
|
||||
if (!op || op->type != LOONGARCH_OP_IMM) {
|
||||
RZ_LOG_WARN("Failed to get operand%d [%d]: \"%s %s\" [imm]\n",
|
||||
idx, loongarch_op_count(ctx->insn), ctx->insn->mnemonic, ctx->insn->op_str);
|
||||
rz_warn_if_reached();
|
||||
return 0;
|
||||
}
|
||||
return op->imm;
|
||||
}
|
||||
|
|
@ -326,12 +326,14 @@ if capstone_dep.version() == 'next'
|
|||
# plugins
|
||||
arch_plugins_list += [
|
||||
'alpha_cs',
|
||||
'loongarch_cs',
|
||||
'xtensa_cs',
|
||||
]
|
||||
|
||||
# plugins sources
|
||||
arch_plugin_sources += [
|
||||
'p/arch_alpha.c',
|
||||
'p/arch_loongarch_cs.c',
|
||||
'p/arch_xtensa_cs.c',
|
||||
]
|
||||
|
||||
|
|
|
|||
2477
librz/arch/p/analysis/analysis_loongarch_cs.c
Normal file
2477
librz/arch/p/analysis/analysis_loongarch_cs.c
Normal file
File diff suppressed because it is too large
Load diff
13
librz/arch/p/arch_loongarch_cs.c
Normal file
13
librz/arch/p/arch_loongarch_cs.c
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
// SPDX-FileCopyrightText: 2024 RizinOrg <info@rizin.re>
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
#include <deprecated_arch_helper.h>
|
||||
|
||||
#include <capstone/capstone.h>
|
||||
#include <capstone/loongarch.h>
|
||||
#include <loongarch/loongarch.inc>
|
||||
|
||||
#include "analysis/analysis_loongarch_cs.c"
|
||||
#include "asm/asm_loongarch_cs.c"
|
||||
|
||||
RZ_ARCH_PLUGIN_DEFINE_DEPRECATED(loongarch_cs);
|
||||
100
librz/arch/p/asm/asm_loongarch_cs.c
Normal file
100
librz/arch/p/asm/asm_loongarch_cs.c
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
// SPDX-FileCopyrightText: 2024 deroad <deroad@kumo.xn--q9jyb4c>
|
||||
// SPDX-License-Identifier: LGPL-3.0-only
|
||||
|
||||
#include <rz_asm.h>
|
||||
#include <rz_lib.h>
|
||||
|
||||
static int disassemble(RzAsm *a, RzAsmOp *op, const ut8 *buf, int len) {
|
||||
if (!buf || !op || !a->plugin_data) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
RzAsmLoongArchContext *ctx = a->plugin_data;
|
||||
if (!loongarch_setup_cs_handle(ctx, a->bits == 64, a->big_endian)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
op->size = 4;
|
||||
ctx->insn = NULL;
|
||||
ctx->count = cs_disasm(ctx->h, (ut8 *)buf, len, a->pc, 1, &ctx->insn);
|
||||
if (ctx->count < 1) {
|
||||
rz_asm_op_set_asm(op, "invalid");
|
||||
goto beach;
|
||||
}
|
||||
if (ctx->insn->size < 1) {
|
||||
return op->size;
|
||||
}
|
||||
|
||||
op->size = ctx->insn->size;
|
||||
rz_asm_op_setf_asm(op, "%s%s%s",
|
||||
ctx->insn->mnemonic, RZ_STR_ISNOTEMPTY(ctx->insn->op_str) ? " " : "", ctx->insn->op_str);
|
||||
|
||||
char *str = rz_asm_op_get_asm(op);
|
||||
if (str) {
|
||||
// remove the '$'<registername> in the string
|
||||
rz_str_replace_char(str, '$', 0);
|
||||
}
|
||||
|
||||
beach:
|
||||
cs_free(ctx->insn, ctx->count);
|
||||
ctx->insn = NULL;
|
||||
ctx->count = 0;
|
||||
return op->size;
|
||||
}
|
||||
|
||||
#define TOKEN(_type, _pat) \
|
||||
do { \
|
||||
RzAsmTokenPattern *pat = RZ_NEW0(RzAsmTokenPattern); \
|
||||
pat->type = RZ_ASM_TOKEN_##_type; \
|
||||
pat->pattern = strdup(_pat); \
|
||||
rz_pvector_push(pvec, pat); \
|
||||
} while (0)
|
||||
|
||||
static bool loongarch_asm_init(void **u) {
|
||||
if (!u) {
|
||||
return false;
|
||||
}
|
||||
// u = RzAsm.plugin_data
|
||||
RzAsmLoongArchContext *ctx = NULL;
|
||||
if (*u) {
|
||||
rz_mem_memzero(*u, sizeof(RzAsmLoongArchContext));
|
||||
ctx = *u;
|
||||
} else {
|
||||
ctx = RZ_NEW0(RzAsmLoongArchContext);
|
||||
if (!ctx) {
|
||||
return false;
|
||||
}
|
||||
*u = ctx;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool loongarch_asm_fini(void *u) {
|
||||
if (!u) {
|
||||
return true;
|
||||
}
|
||||
RzAsmLoongArchContext *ctx = u;
|
||||
cs_close(&ctx->h);
|
||||
free(u);
|
||||
return true;
|
||||
}
|
||||
|
||||
RzAsmPlugin rz_asm_plugin_loongarch_cs = {
|
||||
.name = "loongarch",
|
||||
.desc = "Capstone Alpha disassembler",
|
||||
.license = "LGPL3",
|
||||
.arch = "loongarch",
|
||||
.bits = 32 | 64,
|
||||
.endian = RZ_SYS_ENDIAN_LITTLE | RZ_SYS_ENDIAN_BIG,
|
||||
.disassemble = &disassemble,
|
||||
.init = &loongarch_asm_init,
|
||||
.fini = &loongarch_asm_fini,
|
||||
};
|
||||
|
||||
#ifndef RZ_PLUGIN_INCORE
|
||||
RZ_API RzLibStruct rizin_plugin = {
|
||||
.type = RZ_LIB_TYPE_ASM,
|
||||
.data = &rz_asm_plugin_loongarch_cs,
|
||||
.version = RZ_VERSION
|
||||
};
|
||||
#endif
|
||||
|
|
@ -7,6 +7,9 @@
|
|||
#include "elf.h"
|
||||
|
||||
#define MIPS_PLT_OFFSET 0x20
|
||||
#define LOONGARCH_PLT_OFFSET 0x20
|
||||
#define LOONGARCH_PLT_ENTRY_SIZE 0x10
|
||||
#define RISCV_PLT_OFFSET 0x20
|
||||
#define RISCV_PLT_ENTRY_SIZE 0x10
|
||||
#define RISCV_PLT_OFFSET 0x20
|
||||
#define SPARC_OFFSET_PLT_ENTRY_FROM_GOT_ADDR -0x6
|
||||
|
|
@ -89,6 +92,22 @@ static ut64 get_import_addr_hexagon(ELFOBJ *eo, RzBinElfReloc *rel) {
|
|||
}
|
||||
}
|
||||
|
||||
static ut64 get_import_addr_loongarch(ELFOBJ *bin, RzBinElfReloc *rel) {
|
||||
ut64 got_addr;
|
||||
|
||||
if (!Elf_(rz_bin_elf_get_dt_info)(bin, DT_PLTGOT, &got_addr)) {
|
||||
return UT64_MAX;
|
||||
}
|
||||
|
||||
ut64 plt_addr = get_got_entry(bin, rel);
|
||||
if (plt_addr == UT64_MAX) {
|
||||
return UT64_MAX;
|
||||
}
|
||||
|
||||
ut64 pos = COMPUTE_PLTGOT_POSITION(rel, got_addr, 0x2);
|
||||
return plt_addr + LOONGARCH_PLT_OFFSET + pos * LOONGARCH_PLT_ENTRY_SIZE;
|
||||
}
|
||||
|
||||
static ut64 get_import_addr_riscv(ELFOBJ *bin, RzBinElfReloc *rel) {
|
||||
ut64 got_addr;
|
||||
|
||||
|
|
@ -323,6 +342,8 @@ static ut64 get_import_addr_aux(ELFOBJ *bin, RzBinElfReloc *reloc) {
|
|||
return get_import_addr_x86(bin, reloc);
|
||||
case EM_QDSP6:
|
||||
return get_import_addr_hexagon(bin, reloc);
|
||||
case EM_LOONGARCH:
|
||||
return get_import_addr_loongarch(bin, reloc);
|
||||
default:
|
||||
RZ_LOG_WARN("Unsupported relocs type %" PFMT64u " for arch %d\n",
|
||||
(ut64)reloc->type, bin->ehdr.e_machine);
|
||||
|
|
|
|||
|
|
@ -32,6 +32,11 @@ cs_files = [
|
|||
'arch/HPPA/HPPAInstPrinter.c',
|
||||
'arch/HPPA/HPPAMapping.c',
|
||||
'arch/HPPA/HPPAModule.c',
|
||||
'arch/LoongArch/LoongArchInstPrinter.c',
|
||||
'arch/LoongArch/LoongArchDisassembler.c',
|
||||
'arch/LoongArch/LoongArchMapping.c',
|
||||
'arch/LoongArch/LoongArchDisassemblerExtension.c',
|
||||
'arch/LoongArch/LoongArchModule.c',
|
||||
'arch/M680X/M680XDisassembler.c',
|
||||
'arch/M680X/M680XInstPrinter.c',
|
||||
'arch/M680X/M680XModule.c',
|
||||
|
|
@ -96,20 +101,21 @@ libcapstone_c_args = [
|
|||
'-DCAPSTONE_X86_REDUCE_NO',
|
||||
'-DCAPSTONE_USE_SYS_DYN_MEM',
|
||||
'-DCAPSTONE_DIET_NO',
|
||||
'-DCAPSTONE_HAS_AARCH64',
|
||||
'-DCAPSTONE_HAS_ALPHA',
|
||||
'-DCAPSTONE_HAS_ARM',
|
||||
'-DCAPSTONE_HAS_AARCH64',
|
||||
'-DCAPSTONE_HAS_HPPA',
|
||||
'-DCAPSTONE_HAS_M68K',
|
||||
'-DCAPSTONE_HAS_LOONGARCH',
|
||||
'-DCAPSTONE_HAS_M680X',
|
||||
'-DCAPSTONE_HAS_M68K',
|
||||
'-DCAPSTONE_HAS_MIPS',
|
||||
'-DCAPSTONE_HAS_POWERPC',
|
||||
'-DCAPSTONE_HAS_SPARC',
|
||||
'-DCAPSTONE_HAS_SYSTEMZ',
|
||||
'-DCAPSTONE_HAS_X86',
|
||||
'-DCAPSTONE_HAS_XCORE',
|
||||
'-DCAPSTONE_HAS_TMS320C64X',
|
||||
'-DCAPSTONE_HAS_TRICORE',
|
||||
'-DCAPSTONE_HAS_X86',
|
||||
'-DCAPSTONE_HAS_XCORE',
|
||||
'-DCAPSTONE_HAS_XTENSA',
|
||||
]
|
||||
|
||||
|
|
|
|||
134
test/db/analysis/loongarch
Normal file
134
test/db/analysis/loongarch
Normal file
|
|
@ -0,0 +1,134 @@
|
|||
NAME=loongarch32 ensure correct cpu is selected
|
||||
FILE=bins/elf/hello.loongarch32.o
|
||||
CMDS=<<EOF
|
||||
iI~arch
|
||||
iI~bits
|
||||
aaa
|
||||
pdf @ sym.main
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
arch loongarch
|
||||
machine LoongArch
|
||||
bits 32
|
||||
/ int sym.main(int argc, char **argv, char **envp);
|
||||
| 0x0800005c addi.w sp, sp, -0x20
|
||||
| 0x08000060 st.w ra, sp, 0x1c
|
||||
| 0x08000064 st.w fp, sp, 0x18
|
||||
| 0x08000068 addi.w fp, sp, 0x20
|
||||
| 0x0800006c st.w zero, fp, -0xc
|
||||
| 0x08000070 ori a0, zero, 1
|
||||
| 0x08000074 st.w a0, fp, -0x10
|
||||
| 0x08000078 ori a0, zero, 2
|
||||
| 0x0800007c st.w a0, fp, -0x14
|
||||
| 0x08000080 ld.w a0, fp, -0x10
|
||||
| 0x08000084 ld.w a1, fp, -0x14
|
||||
| 0x08000088 add.w a0, a0, a1
|
||||
| 0x0800008c st.w a0, fp, -0x18
|
||||
| 0x08000090 move a0, zero
|
||||
| 0x08000094 ld.w fp, sp, 0x18
|
||||
| 0x08000098 ld.w ra, sp, 0x1c
|
||||
| 0x0800009c addi.w sp, sp, 0x20
|
||||
\ 0x080000a0 ret
|
||||
EOF
|
||||
RUN
|
||||
|
||||
NAME=loongarch64 ensure correct cpu is selected
|
||||
FILE=bins/elf/ncat.loongarch64
|
||||
CMDS=<<EOF
|
||||
iI~arch
|
||||
iI~bits
|
||||
aaa
|
||||
pdf
|
||||
pdf @ fcn.00010208
|
||||
EOF
|
||||
EXPECT=<<EOF
|
||||
arch loongarch
|
||||
intrp /lib64/ld-linux-loongarch-lp64d.so.1
|
||||
machine LoongArch
|
||||
bits 64
|
||||
/ entry0();
|
||||
| 0x00007508 move a5, a0
|
||||
| 0x0000750c pcalau12i a0, -3
|
||||
| 0x00007510 addi.d t0, zero, 0x3a0
|
||||
| 0x00007514 lu32i.d t0, 0
|
||||
| 0x00007518 lu52i.d t0, t0, 0
|
||||
| 0x0000751c add.d a0, a0, t0
|
||||
| 0x00007520 ld.d a1, sp, 0
|
||||
| 0x00007524 addi.d a2, sp, 8
|
||||
| 0x00007528 bstrins.d sp, zero, 3, 0
|
||||
| 0x0000752c move a3, zero
|
||||
| 0x00007530 move a4, zero
|
||||
| 0x00007534 move a6, sp
|
||||
| 0x00007538 pcalau12i ra, -3
|
||||
| 0x0000753c addi.d t0, zero, 0x340
|
||||
| 0x00007540 lu32i.d t0, 0
|
||||
| ; DATA XREF from fcn.00016abc @ 0x16abc
|
||||
| 0x00007544 lu52i.d t0, t0, 0
|
||||
| 0x00007548 add.d ra, ra, t0
|
||||
| 0x0000754c jirl ra, ra, 0
|
||||
| 0x00007550 pcalau12i ra, -3
|
||||
| 0x00007554 addi.d t0, zero, 0x330
|
||||
| 0x00007558 lu32i.d t0, 0
|
||||
| 0x0000755c lu52i.d t0, t0, 0
|
||||
| 0x00007560 add.d ra, ra, t0
|
||||
| 0x00007564 jirl ra, ra, 0
|
||||
| 0x00007568 nop
|
||||
| 0x0000756c nop
|
||||
| 0x00007570 nop
|
||||
| 0x00007574 nop
|
||||
| 0x00007578 nop
|
||||
\ 0x0000757c nop
|
||||
; XREFS(70)
|
||||
/ fcn.00010208();
|
||||
| 0x00010208 addi.d sp, sp, -0x70
|
||||
| 0x0001020c st.d ra, sp, 0x28
|
||||
| 0x00010210 st.d s0, sp, 0x20
|
||||
| 0x00010214 st.d s1, sp, 0x18
|
||||
| 0x00010218 st.d s2, sp, 0x10
|
||||
| 0x0001021c pcalau12i s1, 0x24
|
||||
| 0x00010220 pcalau12i s0, 0x24
|
||||
| 0x00010224 st.d a4, sp, 0x50
|
||||
| 0x00010228 st.d a5, sp, 0x58
|
||||
| 0x0001022c st.d a6, sp, 0x60
|
||||
| 0x00010230 st.d a7, sp, 0x68
|
||||
| 0x00010234 st.d a1, sp, 0x38
|
||||
| 0x00010238 st.d a2, sp, 0x40
|
||||
| 0x0001023c st.d a3, sp, 0x48
|
||||
| 0x00010240 ld.d s0, s0, -segment.NOTE
|
||||
| 0x00010244 ld.d s1, s1, -0x210
|
||||
| 0x00010248 ldptr.d t1, s1, 0
|
||||
| 0x0001024c move s2, a0
|
||||
| 0x00010250 ldptr.d a0, s0, 0
|
||||
| 0x00010254 pcalau12i t0, 0x24
|
||||
| 0x00010258 pcaddi a3, str.Ncat ; 0x23048 ; "Ncat"
|
||||
| 0x0001025c pcaddi a2, str.s: ; 0x251c8 ; "%s: "
|
||||
| 0x00010260 addi.w a1, zero, 1
|
||||
| 0x00010264 ld.d t0, t0, -0x560
|
||||
| 0x00010268 st.d t1, sp, 8
|
||||
| 0x0001026c jirl ra, t0, 0
|
||||
| 0x00010270 ldptr.d a0, s0, 0
|
||||
| 0x00010274 pcalau12i t0, 0x24
|
||||
| 0x00010278 addi.d a3, sp, 0x38
|
||||
| 0x0001027c move a2, s2
|
||||
| 0x00010280 addi.w a1, zero, 1
|
||||
| 0x00010284 ld.d t0, t0, -0x588
|
||||
| 0x00010288 stptr.d a3, sp, 0
|
||||
| 0x0001028c jirl ra, t0, 0
|
||||
| 0x00010290 ldptr.d a0, s0, 0
|
||||
| 0x00010294 pcalau12i t0, 0x24
|
||||
| 0x00010298 ld.d t0, t0, -0x180
|
||||
| 0x0001029c jirl ra, t0, 0
|
||||
| 0x000102a0 ld.d t1, sp, 8
|
||||
| 0x000102a4 ldptr.d t0, s1, 0
|
||||
| ,=< 0x000102a8 bne t1, t0, 0x1c
|
||||
| | 0x000102ac ld.d ra, sp, 0x28
|
||||
| | 0x000102b0 ld.d s0, sp, 0x20
|
||||
| | 0x000102b4 ld.d s1, sp, 0x18
|
||||
| | 0x000102b8 ld.d s2, sp, 0x10
|
||||
| | 0x000102bc addi.d sp, sp, 0x70
|
||||
| | 0x000102c0 ret
|
||||
| `-> 0x000102c4 pcalau12i t0, 0x24
|
||||
| 0x000102c8 ld.d t0, t0, -0x4a8
|
||||
\ 0x000102cc jirl ra, t0, 0
|
||||
EOF
|
||||
RUN
|
||||
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue