[PATCH v3] LoongArch: Implement physical address with ELF program header

Bibo Mao posted 1 patch 2 months, 2 weeks ago
arch/loongarch/include/asm/addrspace.h |  2 +-
arch/loongarch/kernel/vmlinux.lds.S    | 36 +++++++++++++++++---------
2 files changed, 25 insertions(+), 13 deletions(-)
[PATCH v3] LoongArch: Implement physical address with ELF program header
Posted by Bibo Mao 2 months, 2 weeks ago
With structure elf64_phdr, field p_paddr is physical address of the
segment. And it is convenient for qemu to calculate the physical
address when directly boot ELF kernel image.

Otherwise QEMU needs convert virtual address p_vaddr into physical
address, the conversion logic assumes that DMW method is used where
48 bit physical address is supported. However with direct MMU mapping
method with start address from 0xFFFF800000000000, only 47 bit physical
address is supported. QEMU cannot assume the kernel behavior at kernel
loading stage.

Here add physical address indication in ELF program header, it is
convenient to get physical kernel loading address.

Here is output with command readelf -l vmlinux with patch:
  Elf file type is EXEC (Executable file)
  Entry point 0x90000000015f5000
  There are 2 program headers, starting at offset 64
  Program Headers:
    Type           Offset             VirtAddr           PhysAddr
                   FileSiz            MemSiz              Flags  Align
    LOAD           0x0000000000010000 0x9000000000200000 0x0000000000200000
                   0x000000000293b000 0x0000000002a79b98  RWE    0x10000

And output with command readelf -l vmlinux without the patch:
  Elf file type is EXEC (Executable file)
  Entry point 0x90000000015f5000
  There are 2 program headers, starting at offset 64
  Program Headers:
    Type           Offset             VirtAddr           PhysAddr
                   FileSiz            MemSiz              Flags  Align
    LOAD           0x0000000000010000 0x9000000000200000 0x9000000000200000
                   0x000000000293b000 0x0000000002a79b98  RWE    0x10000

Signed-off-by: Bibo Mao <maobibo@loongson.cn>
---
v2 ... v3:
  1. Fix compile issue where macro PHYS_OFFSET is not defined with assemble
     code.
v1 ... v2:
  1. Set LOAD_OFFSET with PAGE_OFFSET rather than CACHE_BASE, since it
     is generic with PAGE_OFFSET.
  2. Add AT information with missing edata_padding section.
---
 arch/loongarch/include/asm/addrspace.h |  2 +-
 arch/loongarch/kernel/vmlinux.lds.S    | 36 +++++++++++++++++---------
 2 files changed, 25 insertions(+), 13 deletions(-)

diff --git a/arch/loongarch/include/asm/addrspace.h b/arch/loongarch/include/asm/addrspace.h
index e739dbc6329d..18f6c2b469bb 100644
--- a/arch/loongarch/include/asm/addrspace.h
+++ b/arch/loongarch/include/asm/addrspace.h
@@ -18,10 +18,10 @@
 /*
  * This gives the physical RAM offset.
  */
-#ifndef __ASSEMBLER__
 #ifndef PHYS_OFFSET
 #define PHYS_OFFSET	_UL(0)
 #endif
+#ifndef __ASSEMBLER__
 extern unsigned long vm_map_base;
 #endif /* __ASSEMBLER__ */
 
diff --git a/arch/loongarch/kernel/vmlinux.lds.S b/arch/loongarch/kernel/vmlinux.lds.S
index 08ea921cdec1..8ce6b0d948f4 100644
--- a/arch/loongarch/kernel/vmlinux.lds.S
+++ b/arch/loongarch/kernel/vmlinux.lds.S
@@ -3,10 +3,12 @@
 #include <asm/asm-offsets.h>
 #include <asm/thread_info.h>
 #include <asm/orc_lookup.h>
+#include <asm/addrspace.h>
 
 #define PAGE_SIZE _PAGE_SIZE
 #define RO_EXCEPTION_TABLE_ALIGN	4
 #define PHYSADDR_MASK			0xffffffffffff /* 48-bit */
+#define LOAD_OFFSET			PAGE_OFFSET
 
 /*
  * Put .bss..swapper_pg_dir as the first thing in .bss. This will
@@ -42,7 +44,7 @@ SECTIONS
 
 	. = ALIGN(PECOFF_SEGMENT_ALIGN);
 	_stext = .;
-	.text : {
+	.text : AT(ADDR(.text) - LOAD_OFFSET) {
 		TEXT_TEXT
 		SCHED_TEXT
 		LOCK_TEXT
@@ -60,7 +62,7 @@ SECTIONS
 	__inittext_begin = .;
 
 	INIT_TEXT_SECTION(PAGE_SIZE)
-	.exit.text : {
+	.exit.text : AT(ADDR(.exit.text) - LOAD_OFFSET) {
 		EXIT_TEXT
 	}
 
@@ -82,7 +84,7 @@ SECTIONS
 	}
 
 	INIT_DATA_SECTION(16)
-	.exit.data : {
+	.exit.data : AT(ADDR(.exit.data) - LOAD_OFFSET) {
 		EXIT_DATA
 	}
 
@@ -90,7 +92,7 @@ SECTIONS
 	PERCPU_SECTION(1 << CONFIG_L1_CACHE_SHIFT)
 #endif
 
-	.init.bss : {
+	.init.bss : AT(ADDR(.init.bss) - LOAD_OFFSET) {
 		*(.init.bss)
 	}
 	. = ALIGN(PECOFF_SEGMENT_ALIGN);
@@ -101,27 +103,34 @@ SECTIONS
 	_sdata = .;
 	RO_DATA(4096)
 
-	.got : ALIGN(16) { *(.got) }
-	.plt : ALIGN(16) { *(.plt) }
-	.got.plt : ALIGN(16) { *(.got.plt) }
+	. =  ALIGN(16);
+	.got : AT(ADDR(.got) - LOAD_OFFSET) { *(.got) }
+	. =  ALIGN(16);
+	.plt : AT(ADDR(.plt) - LOAD_OFFSET) { *(.plt) }
+	. =  ALIGN(16);
+	.got.plt : AT(ADDR(.got.plt) - LOAD_OFFSET) { *(.got.plt) }
 
 	RW_DATA(1 << CONFIG_L1_CACHE_SHIFT, PAGE_SIZE, THREAD_SIZE)
 
-	.rela.dyn : ALIGN(8) {
+	. = ALIGN(8);
+	.rela.dyn : AT(ADDR(.rela.dyn) - LOAD_OFFSET) {
 		__rela_dyn_begin = .;
 		 *(.rela.dyn) *(.rela*)
 		__rela_dyn_end = .;
 	}
 
 #ifdef CONFIG_RELR
-	.relr.dyn : ALIGN(8) {
+	. = ALIGN(8);
+	.relr.dyn : AT(ADDR(.relr.dyn) - LOAD_OFFSET) {
 		__relr_dyn_begin = .;
 		 *(.relr.dyn)
 		__relr_dyn_end = .;
 	}
 #endif
 
-	.data.rel : { *(.data.rel*) }
+	.data.rel : AT(ADDR(.data.rel) - LOAD_OFFSET) {
+		*(.data.rel*)
+	}
 
 #ifdef CONFIG_RELOCATABLE
 	. = ALIGN(8);
@@ -134,10 +143,13 @@ SECTIONS
 
 	ORC_UNWIND_TABLE
 
-	.sdata : {
+	.sdata : AT(ADDR(.sdata) - LOAD_OFFSET) {
 		*(.sdata)
 	}
-	.edata_padding : { BYTE(0); . = ALIGN(PECOFF_FILE_ALIGN); }
+	.edata_padding : AT(ADDR(.edata_padding) - LOAD_OFFSET) {
+		BYTE(0);
+		. = ALIGN(PECOFF_FILE_ALIGN);
+	}
 	_edata =  .;
 
 	BSS_SECTION(0, SZ_64K, 8)

base-commit: 89be9a83ccf1f88522317ce02f854f30d6115c41
-- 
2.39.3
Re: [PATCH v3] LoongArch: Implement physical address with ELF program header
Posted by Jiaxun Yang 2 months ago

> 2025年7月23日 16:06,Bibo Mao <maobibo@loongson.cn> 写道:
> 
> With structure elf64_phdr, field p_paddr is physical address of the
> segment. And it is convenient for qemu to calculate the physical
> address when directly boot ELF kernel image.
> 
> Otherwise QEMU needs convert virtual address p_vaddr into physical
> address, the conversion logic assumes that DMW method is used where
> 48 bit physical address is supported. However with direct MMU mapping
> method with start address from 0xFFFF800000000000, only 47 bit physical
> address is supported. QEMU cannot assume the kernel behavior at kernel
> loading stage.
> 
> Here add physical address indication in ELF program header, it is
> convenient to get physical kernel loading address.

Hi Bibo,

Thanks for your patch. Unfortunately it breaks PMON’s DWARF debugging
Feature, causing exception on list symbols.

I’ll try to investigate.

Thanks
Jiaxun



> 
> Here is output with command readelf -l vmlinux with patch:
> Elf file type is EXEC (Executable file)
> Entry point 0x90000000015f5000
> There are 2 program headers, starting at offset 64
> Program Headers:
>   Type           Offset             VirtAddr           PhysAddr
>                  FileSiz            MemSiz              Flags  Align
>   LOAD           0x0000000000010000 0x9000000000200000 0x0000000000200000
>                  0x000000000293b000 0x0000000002a79b98  RWE    0x10000
> 
> And output with command readelf -l vmlinux without the patch:
> Elf file type is EXEC (Executable file)
> Entry point 0x90000000015f5000
> There are 2 program headers, starting at offset 64
> Program Headers:
>   Type           Offset             VirtAddr           PhysAddr
>                  FileSiz            MemSiz              Flags  Align
>   LOAD           0x0000000000010000 0x9000000000200000 0x9000000000200000
>                  0x000000000293b000 0x0000000002a79b98  RWE    0x10000
> 
> Signed-off-by: Bibo Mao <maobibo@loongson.cn>
> ---
> v2 ... v3:
> 1. Fix compile issue where macro PHYS_OFFSET is not defined with assemble
>    code.
> v1 ... v2:
> 1. Set LOAD_OFFSET with PAGE_OFFSET rather than CACHE_BASE, since it
>    is generic with PAGE_OFFSET.
> 2. Add AT information with missing edata_padding section.
> ---
> arch/loongarch/include/asm/addrspace.h |  2 +-
> arch/loongarch/kernel/vmlinux.lds.S    | 36 +++++++++++++++++---------
> 2 files changed, 25 insertions(+), 13 deletions(-)
> 
> diff --git a/arch/loongarch/include/asm/addrspace.h b/arch/loongarch/include/asm/addrspace.h
> index e739dbc6329d..18f6c2b469bb 100644
> --- a/arch/loongarch/include/asm/addrspace.h
> +++ b/arch/loongarch/include/asm/addrspace.h
> @@ -18,10 +18,10 @@
> /*
> * This gives the physical RAM offset.
> */
> -#ifndef __ASSEMBLER__
> #ifndef PHYS_OFFSET
> #define PHYS_OFFSET	_UL(0)
> #endif
> +#ifndef __ASSEMBLER__
> extern unsigned long vm_map_base;
> #endif /* __ASSEMBLER__ */
> 
> diff --git a/arch/loongarch/kernel/vmlinux.lds.S b/arch/loongarch/kernel/vmlinux.lds.S
> index 08ea921cdec1..8ce6b0d948f4 100644
> --- a/arch/loongarch/kernel/vmlinux.lds.S
> +++ b/arch/loongarch/kernel/vmlinux.lds.S
> @@ -3,10 +3,12 @@
> #include <asm/asm-offsets.h>
> #include <asm/thread_info.h>
> #include <asm/orc_lookup.h>
> +#include <asm/addrspace.h>
> 
> #define PAGE_SIZE _PAGE_SIZE
> #define RO_EXCEPTION_TABLE_ALIGN	4
> #define PHYSADDR_MASK			0xffffffffffff /* 48-bit */
> +#define LOAD_OFFSET			PAGE_OFFSET
> 
> /*
> * Put .bss..swapper_pg_dir as the first thing in .bss. This will
> @@ -42,7 +44,7 @@ SECTIONS
> 
> 	. = ALIGN(PECOFF_SEGMENT_ALIGN);
> 	_stext = .;
> -	.text : {
> +	.text : AT(ADDR(.text) - LOAD_OFFSET) {
> 		TEXT_TEXT
> 		SCHED_TEXT
> 		LOCK_TEXT
> @@ -60,7 +62,7 @@ SECTIONS
> 	__inittext_begin = .;
> 
> 	INIT_TEXT_SECTION(PAGE_SIZE)
> -	.exit.text : {
> +	.exit.text : AT(ADDR(.exit.text) - LOAD_OFFSET) {
> 		EXIT_TEXT
> 	}
> 
> @@ -82,7 +84,7 @@ SECTIONS
> 	}
> 
> 	INIT_DATA_SECTION(16)
> -	.exit.data : {
> +	.exit.data : AT(ADDR(.exit.data) - LOAD_OFFSET) {
> 		EXIT_DATA
> 	}
> 
> @@ -90,7 +92,7 @@ SECTIONS
> 	PERCPU_SECTION(1 << CONFIG_L1_CACHE_SHIFT)
> #endif
> 
> -	.init.bss : {
> +	.init.bss : AT(ADDR(.init.bss) - LOAD_OFFSET) {
> 		*(.init.bss)
> 	}
> 	. = ALIGN(PECOFF_SEGMENT_ALIGN);
> @@ -101,27 +103,34 @@ SECTIONS
> 	_sdata = .;
> 	RO_DATA(4096)
> 
> -	.got : ALIGN(16) { *(.got) }
> -	.plt : ALIGN(16) { *(.plt) }
> -	.got.plt : ALIGN(16) { *(.got.plt) }
> +	. =  ALIGN(16);
> +	.got : AT(ADDR(.got) - LOAD_OFFSET) { *(.got) }
> +	. =  ALIGN(16);
> +	.plt : AT(ADDR(.plt) - LOAD_OFFSET) { *(.plt) }
> +	. =  ALIGN(16);
> +	.got.plt : AT(ADDR(.got.plt) - LOAD_OFFSET) { *(.got.plt) }
> 
> 	RW_DATA(1 << CONFIG_L1_CACHE_SHIFT, PAGE_SIZE, THREAD_SIZE)
> 
> -	.rela.dyn : ALIGN(8) {
> +	. = ALIGN(8);
> +	.rela.dyn : AT(ADDR(.rela.dyn) - LOAD_OFFSET) {
> 		__rela_dyn_begin = .;
> 		 *(.rela.dyn) *(.rela*)
> 		__rela_dyn_end = .;
> 	}
> 
> #ifdef CONFIG_RELR
> -	.relr.dyn : ALIGN(8) {
> +	. = ALIGN(8);
> +	.relr.dyn : AT(ADDR(.relr.dyn) - LOAD_OFFSET) {
> 		__relr_dyn_begin = .;
> 		 *(.relr.dyn)
> 		__relr_dyn_end = .;
> 	}
> #endif
> 
> -	.data.rel : { *(.data.rel*) }
> +	.data.rel : AT(ADDR(.data.rel) - LOAD_OFFSET) {
> +		*(.data.rel*)
> +	}
> 
> #ifdef CONFIG_RELOCATABLE
> 	. = ALIGN(8);
> @@ -134,10 +143,13 @@ SECTIONS
> 
> 	ORC_UNWIND_TABLE
> 
> -	.sdata : {
> +	.sdata : AT(ADDR(.sdata) - LOAD_OFFSET) {
> 		*(.sdata)
> 	}
> -	.edata_padding : { BYTE(0); . = ALIGN(PECOFF_FILE_ALIGN); }
> +	.edata_padding : AT(ADDR(.edata_padding) - LOAD_OFFSET) {
> +		BYTE(0);
> +		. = ALIGN(PECOFF_FILE_ALIGN);
> +	}
> 	_edata =  .;
> 
> 	BSS_SECTION(0, SZ_64K, 8)
> 
> base-commit: 89be9a83ccf1f88522317ce02f854f30d6115c41
> -- 
> 2.39.3
> 
> 
Re: [PATCH v3] LoongArch: Implement physical address with ELF program header
Posted by Bibo Mao 2 months ago

On 2025/8/1 下午4:57, Jiaxun Yang wrote:
> 
> 
>> 2025年7月23日 16:06,Bibo Mao <maobibo@loongson.cn> 写道:
>>
>> With structure elf64_phdr, field p_paddr is physical address of the
>> segment. And it is convenient for qemu to calculate the physical
>> address when directly boot ELF kernel image.
>>
>> Otherwise QEMU needs convert virtual address p_vaddr into physical
>> address, the conversion logic assumes that DMW method is used where
>> 48 bit physical address is supported. However with direct MMU mapping
>> method with start address from 0xFFFF800000000000, only 47 bit physical
>> address is supported. QEMU cannot assume the kernel behavior at kernel
>> loading stage.
>>
>> Here add physical address indication in ELF program header, it is
>> convenient to get physical kernel loading address.
> 
> Hi Bibo,
> 
> Thanks for your patch. Unfortunately it breaks PMON’s DWARF debugging
> Feature, causing exception on list symbols.
> 
> I’ll try to investigate.
Hi Jiaxun.

Thanks for reporting it. Could you describe the problem with more 
detailed information? such as which command of PMON etc.

Regards
Bibo Mao
> 
> Thanks
> Jiaxun
> 
> 
> 
>>
>> Here is output with command readelf -l vmlinux with patch:
>> Elf file type is EXEC (Executable file)
>> Entry point 0x90000000015f5000
>> There are 2 program headers, starting at offset 64
>> Program Headers:
>>    Type           Offset             VirtAddr           PhysAddr
>>                   FileSiz            MemSiz              Flags  Align
>>    LOAD           0x0000000000010000 0x9000000000200000 0x0000000000200000
>>                   0x000000000293b000 0x0000000002a79b98  RWE    0x10000
>>
>> And output with command readelf -l vmlinux without the patch:
>> Elf file type is EXEC (Executable file)
>> Entry point 0x90000000015f5000
>> There are 2 program headers, starting at offset 64
>> Program Headers:
>>    Type           Offset             VirtAddr           PhysAddr
>>                   FileSiz            MemSiz              Flags  Align
>>    LOAD           0x0000000000010000 0x9000000000200000 0x9000000000200000
>>                   0x000000000293b000 0x0000000002a79b98  RWE    0x10000
>>
>> Signed-off-by: Bibo Mao <maobibo@loongson.cn>
>> ---
>> v2 ... v3:
>> 1. Fix compile issue where macro PHYS_OFFSET is not defined with assemble
>>     code.
>> v1 ... v2:
>> 1. Set LOAD_OFFSET with PAGE_OFFSET rather than CACHE_BASE, since it
>>     is generic with PAGE_OFFSET.
>> 2. Add AT information with missing edata_padding section.
>> ---
>> arch/loongarch/include/asm/addrspace.h |  2 +-
>> arch/loongarch/kernel/vmlinux.lds.S    | 36 +++++++++++++++++---------
>> 2 files changed, 25 insertions(+), 13 deletions(-)
>>
>> diff --git a/arch/loongarch/include/asm/addrspace.h b/arch/loongarch/include/asm/addrspace.h
>> index e739dbc6329d..18f6c2b469bb 100644
>> --- a/arch/loongarch/include/asm/addrspace.h
>> +++ b/arch/loongarch/include/asm/addrspace.h
>> @@ -18,10 +18,10 @@
>> /*
>> * This gives the physical RAM offset.
>> */
>> -#ifndef __ASSEMBLER__
>> #ifndef PHYS_OFFSET
>> #define PHYS_OFFSET	_UL(0)
>> #endif
>> +#ifndef __ASSEMBLER__
>> extern unsigned long vm_map_base;
>> #endif /* __ASSEMBLER__ */
>>
>> diff --git a/arch/loongarch/kernel/vmlinux.lds.S b/arch/loongarch/kernel/vmlinux.lds.S
>> index 08ea921cdec1..8ce6b0d948f4 100644
>> --- a/arch/loongarch/kernel/vmlinux.lds.S
>> +++ b/arch/loongarch/kernel/vmlinux.lds.S
>> @@ -3,10 +3,12 @@
>> #include <asm/asm-offsets.h>
>> #include <asm/thread_info.h>
>> #include <asm/orc_lookup.h>
>> +#include <asm/addrspace.h>
>>
>> #define PAGE_SIZE _PAGE_SIZE
>> #define RO_EXCEPTION_TABLE_ALIGN	4
>> #define PHYSADDR_MASK			0xffffffffffff /* 48-bit */
>> +#define LOAD_OFFSET			PAGE_OFFSET
>>
>> /*
>> * Put .bss..swapper_pg_dir as the first thing in .bss. This will
>> @@ -42,7 +44,7 @@ SECTIONS
>>
>> 	. = ALIGN(PECOFF_SEGMENT_ALIGN);
>> 	_stext = .;
>> -	.text : {
>> +	.text : AT(ADDR(.text) - LOAD_OFFSET) {
>> 		TEXT_TEXT
>> 		SCHED_TEXT
>> 		LOCK_TEXT
>> @@ -60,7 +62,7 @@ SECTIONS
>> 	__inittext_begin = .;
>>
>> 	INIT_TEXT_SECTION(PAGE_SIZE)
>> -	.exit.text : {
>> +	.exit.text : AT(ADDR(.exit.text) - LOAD_OFFSET) {
>> 		EXIT_TEXT
>> 	}
>>
>> @@ -82,7 +84,7 @@ SECTIONS
>> 	}
>>
>> 	INIT_DATA_SECTION(16)
>> -	.exit.data : {
>> +	.exit.data : AT(ADDR(.exit.data) - LOAD_OFFSET) {
>> 		EXIT_DATA
>> 	}
>>
>> @@ -90,7 +92,7 @@ SECTIONS
>> 	PERCPU_SECTION(1 << CONFIG_L1_CACHE_SHIFT)
>> #endif
>>
>> -	.init.bss : {
>> +	.init.bss : AT(ADDR(.init.bss) - LOAD_OFFSET) {
>> 		*(.init.bss)
>> 	}
>> 	. = ALIGN(PECOFF_SEGMENT_ALIGN);
>> @@ -101,27 +103,34 @@ SECTIONS
>> 	_sdata = .;
>> 	RO_DATA(4096)
>>
>> -	.got : ALIGN(16) { *(.got) }
>> -	.plt : ALIGN(16) { *(.plt) }
>> -	.got.plt : ALIGN(16) { *(.got.plt) }
>> +	. =  ALIGN(16);
>> +	.got : AT(ADDR(.got) - LOAD_OFFSET) { *(.got) }
>> +	. =  ALIGN(16);
>> +	.plt : AT(ADDR(.plt) - LOAD_OFFSET) { *(.plt) }
>> +	. =  ALIGN(16);
>> +	.got.plt : AT(ADDR(.got.plt) - LOAD_OFFSET) { *(.got.plt) }
>>
>> 	RW_DATA(1 << CONFIG_L1_CACHE_SHIFT, PAGE_SIZE, THREAD_SIZE)
>>
>> -	.rela.dyn : ALIGN(8) {
>> +	. = ALIGN(8);
>> +	.rela.dyn : AT(ADDR(.rela.dyn) - LOAD_OFFSET) {
>> 		__rela_dyn_begin = .;
>> 		 *(.rela.dyn) *(.rela*)
>> 		__rela_dyn_end = .;
>> 	}
>>
>> #ifdef CONFIG_RELR
>> -	.relr.dyn : ALIGN(8) {
>> +	. = ALIGN(8);
>> +	.relr.dyn : AT(ADDR(.relr.dyn) - LOAD_OFFSET) {
>> 		__relr_dyn_begin = .;
>> 		 *(.relr.dyn)
>> 		__relr_dyn_end = .;
>> 	}
>> #endif
>>
>> -	.data.rel : { *(.data.rel*) }
>> +	.data.rel : AT(ADDR(.data.rel) - LOAD_OFFSET) {
>> +		*(.data.rel*)
>> +	}
>>
>> #ifdef CONFIG_RELOCATABLE
>> 	. = ALIGN(8);
>> @@ -134,10 +143,13 @@ SECTIONS
>>
>> 	ORC_UNWIND_TABLE
>>
>> -	.sdata : {
>> +	.sdata : AT(ADDR(.sdata) - LOAD_OFFSET) {
>> 		*(.sdata)
>> 	}
>> -	.edata_padding : { BYTE(0); . = ALIGN(PECOFF_FILE_ALIGN); }
>> +	.edata_padding : AT(ADDR(.edata_padding) - LOAD_OFFSET) {
>> +		BYTE(0);
>> +		. = ALIGN(PECOFF_FILE_ALIGN);
>> +	}
>> 	_edata =  .;
>>
>> 	BSS_SECTION(0, SZ_64K, 8)
>>
>> base-commit: 89be9a83ccf1f88522317ce02f854f30d6115c41
>> -- 
>> 2.39.3
>>
>>