mirror of
https://github.com/upx/upx
synced 2025-09-28 19:06:07 +08:00
PackMachPPC32
This commit is contained in:
parent
2132a28a17
commit
99be1e798b
152
src/p_mach.cpp
152
src/p_mach.cpp
|
@ -39,13 +39,20 @@ static const
|
||||||
static const
|
static const
|
||||||
#include "stub/powerpc-darwin.macho-fold.h"
|
#include "stub/powerpc-darwin.macho-fold.h"
|
||||||
|
|
||||||
|
static const
|
||||||
|
#include "stub/i386-darwin.macho-entry.h"
|
||||||
|
static const
|
||||||
|
#include "stub/i386-darwin.macho-fold.h"
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
PackMachBase<T>::PackMachBase(InputFile *f, unsigned flavor, unsigned count,
|
PackMachBase<T>::PackMachBase(InputFile *f, unsigned cputype, unsigned flavor,
|
||||||
unsigned size) :
|
unsigned count, unsigned size) :
|
||||||
super(f), my_thread_flavor(flavor), my_thread_state_word_count(count),
|
super(f), my_cputype(cputype), my_thread_flavor(flavor),
|
||||||
my_thread_command_size(size),
|
my_thread_state_word_count(count), my_thread_command_size(size),
|
||||||
n_segment(0), rawmseg(0), msegcmd(0)
|
n_segment(0), rawmseg(0), msegcmd(0)
|
||||||
{
|
{
|
||||||
|
MachClass::compileTimeAssertions();
|
||||||
|
bele = N_BELE_CTP::getRTP<BeLePolicy>();
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
|
@ -55,7 +62,8 @@ PackMachBase<T>::~PackMachBase()
|
||||||
delete [] rawmseg;
|
delete [] rawmseg;
|
||||||
}
|
}
|
||||||
|
|
||||||
const int *PackMachPPC32::getCompressionMethods(int /*method*/, int /*level*/) const
|
template <class T>
|
||||||
|
const int *PackMachBase<T>::getCompressionMethods(int /*method*/, int /*level*/) const
|
||||||
{
|
{
|
||||||
// There really is no LE bias in M_NRV2E_LE32.
|
// There really is no LE bias in M_NRV2E_LE32.
|
||||||
static const int m_nrv2e[] = { M_NRV2E_LE32, M_END };
|
static const int m_nrv2e[] = { M_NRV2E_LE32, M_END };
|
||||||
|
@ -69,11 +77,22 @@ const int *PackMachPPC32::getFilters() const
|
||||||
return filters;
|
return filters;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int const *PackMachI386::getFilters() const
|
||||||
|
{
|
||||||
|
static const int filters[] = { 0x49, FT_END };
|
||||||
|
return filters;
|
||||||
|
}
|
||||||
|
|
||||||
Linker *PackMachPPC32::newLinker() const
|
Linker *PackMachPPC32::newLinker() const
|
||||||
{
|
{
|
||||||
return new ElfLinkerPpc32;
|
return new ElfLinkerPpc32;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Linker *PackMachI386::newLinker() const
|
||||||
|
{
|
||||||
|
return new ElfLinkerX86;
|
||||||
|
}
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
void
|
void
|
||||||
PackMachBase<T>::addStubEntrySections(Filter const *)
|
PackMachBase<T>::addStubEntrySections(Filter const *)
|
||||||
|
@ -89,6 +108,68 @@ PackMachBase<T>::addStubEntrySections(Filter const *)
|
||||||
addLoader("ELFMAINY,IDENTSTR,+40,ELFMAINZ,FOLDEXEC", NULL);
|
addLoader("ELFMAINY,IDENTSTR,+40,ELFMAINZ,FOLDEXEC", NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PackMachI386::addStubEntrySections(Filter const *ft)
|
||||||
|
{
|
||||||
|
int const n_mru = ft->n_mru; // FIXME: belongs to filter? packerf?
|
||||||
|
|
||||||
|
// entry to stub
|
||||||
|
addLoader("LEXEC000", NULL);
|
||||||
|
|
||||||
|
if (ft->id) {
|
||||||
|
{ // decompr, unfilter are separate
|
||||||
|
addLoader("LXUNF000", NULL);
|
||||||
|
addLoader("LXUNF002", NULL);
|
||||||
|
if (0x80==(ft->id & 0xF0)) {
|
||||||
|
if (256==n_mru) {
|
||||||
|
addLoader("MRUBYTE0", NULL);
|
||||||
|
}
|
||||||
|
else if (n_mru) {
|
||||||
|
addLoader("LXMRU005", NULL);
|
||||||
|
}
|
||||||
|
if (n_mru) {
|
||||||
|
addLoader("LXMRU006", NULL);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
addLoader("LXMRU007", NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (0x40==(ft->id & 0xF0)) {
|
||||||
|
addLoader("LXUNF008", NULL);
|
||||||
|
}
|
||||||
|
addLoader("LXUNF010", NULL);
|
||||||
|
}
|
||||||
|
if (n_mru) {
|
||||||
|
addLoader("LEXEC009", NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addLoader("LEXEC010", NULL);
|
||||||
|
addLoader(getDecompressorSections(), NULL);
|
||||||
|
addLoader("LEXEC015", NULL);
|
||||||
|
if (ft->id) {
|
||||||
|
{ // decompr, unfilter are separate
|
||||||
|
if (0x80!=(ft->id & 0xF0)) {
|
||||||
|
addLoader("LXUNF042", NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addFilter32(ft->id);
|
||||||
|
{ // decompr, unfilter are separate
|
||||||
|
if (0x80==(ft->id & 0xF0)) {
|
||||||
|
if (0==n_mru) {
|
||||||
|
addLoader("LXMRU058", NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
addLoader("LXUNF035", NULL);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
addLoader("LEXEC017", NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
addLoader("IDENTSTR", NULL);
|
||||||
|
addLoader("LEXEC020", NULL);
|
||||||
|
addLoader("FOLDEXEC", NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
void PackMachBase<T>::defineSymbols(Filter const *)
|
void PackMachBase<T>::defineSymbols(Filter const *)
|
||||||
|
@ -151,6 +232,14 @@ PackMachPPC32::buildLoader(const Filter *ft)
|
||||||
stub_powerpc_darwin_macho_fold, sizeof(stub_powerpc_darwin_macho_fold), ft );
|
stub_powerpc_darwin_macho_fold, sizeof(stub_powerpc_darwin_macho_fold), ft );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
PackMachI386::buildLoader(const Filter *ft)
|
||||||
|
{
|
||||||
|
buildMachLoader(
|
||||||
|
stub_i386_darwin_macho_entry, sizeof(stub_i386_darwin_macho_entry),
|
||||||
|
stub_i386_darwin_macho_fold, sizeof(stub_i386_darwin_macho_fold), ft );
|
||||||
|
}
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
void PackMachBase<T>::patchLoader() { }
|
void PackMachBase<T>::patchLoader() { }
|
||||||
|
|
||||||
|
@ -201,6 +290,20 @@ void PackMachPPC32::pack4(OutputFile *fo, Filter &ft) // append PackHeader
|
||||||
fo->write(&linfo, sizeof(linfo));
|
fo->write(&linfo, sizeof(linfo));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PackMachI386::pack4(OutputFile *fo, Filter &ft) // append PackHeader
|
||||||
|
{
|
||||||
|
// offset of p_info in compressed file
|
||||||
|
overlay_offset = sizeof(mhdro) + sizeof(segcmdo) + sizeof(threado) + sizeof(linfo);
|
||||||
|
|
||||||
|
super::pack4(fo, ft);
|
||||||
|
segcmdo.filesize = fo->getBytesWritten();
|
||||||
|
segcmdo.vmsize += segcmdo.filesize;
|
||||||
|
fo->seek(sizeof(mhdro), SEEK_SET);
|
||||||
|
fo->write(&segcmdo, sizeof(segcmdo));
|
||||||
|
fo->write(&threado, sizeof(threado));
|
||||||
|
fo->write(&linfo, sizeof(linfo));
|
||||||
|
}
|
||||||
|
|
||||||
void PackMachPPC32::pack3(OutputFile *fo, Filter &ft) // append loader
|
void PackMachPPC32::pack3(OutputFile *fo, Filter &ft) // append loader
|
||||||
{
|
{
|
||||||
BE32 disp;
|
BE32 disp;
|
||||||
|
@ -208,13 +311,27 @@ void PackMachPPC32::pack3(OutputFile *fo, Filter &ft) // append loader
|
||||||
unsigned len = fo->getBytesWritten();
|
unsigned len = fo->getBytesWritten();
|
||||||
fo->write(&zero, 3& (0u-len));
|
fo->write(&zero, 3& (0u-len));
|
||||||
len += (3& (0u-len)) + sizeof(disp);
|
len += (3& (0u-len)) + sizeof(disp);
|
||||||
set_be32(&disp, 4+ len - sz_mach_headers); // 4: sizeof(instruction)
|
disp = 4+ len - sz_mach_headers; // 4: sizeof(instruction)
|
||||||
fo->write(&disp, sizeof(disp));
|
fo->write(&disp, sizeof(disp));
|
||||||
|
|
||||||
threado.state.srr0 = len + segcmdo.vmaddr; /* entry address */
|
threado.state.srr0 = len + segcmdo.vmaddr; /* entry address */
|
||||||
super::pack3(fo, ft);
|
super::pack3(fo, ft);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PackMachI386::pack3(OutputFile *fo, Filter &ft) // append loader
|
||||||
|
{
|
||||||
|
LE32 disp;
|
||||||
|
unsigned const zero = 0;
|
||||||
|
unsigned len = fo->getBytesWritten();
|
||||||
|
fo->write(&zero, 3& (0u-len));
|
||||||
|
len += (3& (0u-len)) + sizeof(disp);
|
||||||
|
disp = 4+ len - sz_mach_headers; // 4: sizeof(instruction)
|
||||||
|
fo->write(&disp, sizeof(disp));
|
||||||
|
|
||||||
|
threado.state.eip = len + segcmdo.vmaddr; /* entry address */
|
||||||
|
super::pack3(fo, ft);
|
||||||
|
}
|
||||||
|
|
||||||
// Determine length of gap between PT_LOAD phdri[k] and closest PT_LOAD
|
// Determine length of gap between PT_LOAD phdri[k] and closest PT_LOAD
|
||||||
// which follows in the file (or end-of-file). Optimize for common case
|
// which follows in the file (or end-of-file). Optimize for common case
|
||||||
// where the PT_LOAD are adjacent ascending by .p_offset. Assume no overlap.
|
// where the PT_LOAD are adjacent ascending by .p_offset. Assume no overlap.
|
||||||
|
@ -228,8 +345,7 @@ unsigned PackMachBase<T>::find_SEGMENT_gap(
|
||||||
|| 0==msegcmd[k].filesize ) {
|
|| 0==msegcmd[k].filesize ) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
unsigned const hi = get_native32(&msegcmd[k].fileoff) +
|
unsigned const hi = msegcmd[k].fileoff + msegcmd[k].filesize;
|
||||||
get_native32(&msegcmd[k].filesize);
|
|
||||||
unsigned lo = ph.u_file_size;
|
unsigned lo = ph.u_file_size;
|
||||||
unsigned j = k;
|
unsigned j = k;
|
||||||
for (;;) { // circular search, optimize for adjacent ascending
|
for (;;) { // circular search, optimize for adjacent ascending
|
||||||
|
@ -242,7 +358,7 @@ unsigned PackMachBase<T>::find_SEGMENT_gap(
|
||||||
}
|
}
|
||||||
if (Mach_segment_command::LC_SEGMENT==msegcmd[j].cmd
|
if (Mach_segment_command::LC_SEGMENT==msegcmd[j].cmd
|
||||||
&& 0!=msegcmd[j].filesize ) {
|
&& 0!=msegcmd[j].filesize ) {
|
||||||
unsigned const t = get_native32(&msegcmd[j].fileoff);
|
unsigned const t = msegcmd[j].fileoff;
|
||||||
if ((t - hi) < (lo - hi)) {
|
if ((t - hi) < (lo - hi)) {
|
||||||
lo = t;
|
lo = t;
|
||||||
if (hi==lo) {
|
if (hi==lo) {
|
||||||
|
@ -313,8 +429,7 @@ void PackMachBase<T>::pack2(OutputFile *fo, Filter &ft) // append compressed bo
|
||||||
for (k = 0; k < n_segment; ++k) {
|
for (k = 0; k < n_segment; ++k) {
|
||||||
x.size = find_SEGMENT_gap(k);
|
x.size = find_SEGMENT_gap(k);
|
||||||
if (x.size) {
|
if (x.size) {
|
||||||
x.offset = get_native32(&msegcmd[k].fileoff) +
|
x.offset = msegcmd[k].fileoff +msegcmd[k].filesize;
|
||||||
get_native32(&msegcmd[k].filesize);
|
|
||||||
packExtent(x, total_in, total_out, 0, fo);
|
packExtent(x, total_in, total_out, 0, fo);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -339,6 +454,16 @@ void PackMachPPC32::pack1_setup_threado(OutputFile *const fo)
|
||||||
fo->write(&threado, sizeof(threado));
|
fo->write(&threado, sizeof(threado));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void PackMachI386::pack1_setup_threado(OutputFile *const fo)
|
||||||
|
{
|
||||||
|
threado.cmd = Mach_segment_command::LC_UNIXTHREAD;
|
||||||
|
threado.cmdsize = sizeof(threado);
|
||||||
|
threado.flavor = my_thread_flavor;
|
||||||
|
threado.count = my_thread_state_word_count;
|
||||||
|
memset(&threado.state, 0, sizeof(threado.state));
|
||||||
|
fo->write(&threado, sizeof(threado));
|
||||||
|
}
|
||||||
|
|
||||||
template <class T>
|
template <class T>
|
||||||
void PackMachBase<T>::pack1(OutputFile *const fo, Filter &/*ft*/) // generate executable header
|
void PackMachBase<T>::pack1(OutputFile *const fo, Filter &/*ft*/) // generate executable header
|
||||||
{
|
{
|
||||||
|
@ -433,8 +558,7 @@ void PackMachBase<T>::unpack(OutputFile *fo)
|
||||||
for (unsigned j = 0; j < ncmds; ++j) {
|
for (unsigned j = 0; j < ncmds; ++j) {
|
||||||
unsigned const size = find_SEGMENT_gap(j);
|
unsigned const size = find_SEGMENT_gap(j);
|
||||||
if (size) {
|
if (size) {
|
||||||
unsigned const where = get_native32(&msegcmd[k].fileoff) +
|
unsigned const where = msegcmd[k].fileoff +msegcmd[k].filesize;
|
||||||
get_native32(&msegcmd[k].filesize);
|
|
||||||
if (fo)
|
if (fo)
|
||||||
fo->seek(where, SEEK_SET);
|
fo->seek(where, SEEK_SET);
|
||||||
unpackExtent(size, fo, total_in, total_out,
|
unpackExtent(size, fo, total_in, total_out,
|
||||||
|
@ -451,7 +575,7 @@ bool PackMachBase<T>::canPack()
|
||||||
fi->readx(&mhdri, sizeof(mhdri));
|
fi->readx(&mhdri, sizeof(mhdri));
|
||||||
|
|
||||||
if (Mach_header::MH_MAGIC !=mhdri.magic
|
if (Mach_header::MH_MAGIC !=mhdri.magic
|
||||||
|| Mach_header::CPU_TYPE_POWERPC !=mhdri.cputype
|
|| my_cputype !=mhdri.cputype
|
||||||
|| Mach_header::MH_EXECUTE !=mhdri.filetype
|
|| Mach_header::MH_EXECUTE !=mhdri.filetype
|
||||||
)
|
)
|
||||||
return false;
|
return false;
|
||||||
|
|
22
src/p_mach.h
22
src/p_mach.h
|
@ -245,6 +245,10 @@ struct MachClass_32
|
||||||
typedef N_Mach::Mach_section_command<MachITypes> Mach_section_command;
|
typedef N_Mach::Mach_section_command<MachITypes> Mach_section_command;
|
||||||
typedef N_Mach::Mach_ppc_thread_state<MachITypes> Mach_ppc_thread_state;
|
typedef N_Mach::Mach_ppc_thread_state<MachITypes> Mach_ppc_thread_state;
|
||||||
typedef N_Mach::Mach_i386_thread_state<MachITypes> Mach_i386_thread_state;
|
typedef N_Mach::Mach_i386_thread_state<MachITypes> Mach_i386_thread_state;
|
||||||
|
|
||||||
|
static void compileTimeAssertions() {
|
||||||
|
BeLePolicy::compileTimeAssertions();
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
template <class TP>
|
template <class TP>
|
||||||
|
@ -316,9 +320,10 @@ protected:
|
||||||
typedef typename MachClass::Mach_section_command Mach_section_command;
|
typedef typename MachClass::Mach_section_command Mach_section_command;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PackMachBase(InputFile *, unsigned t_flavor, unsigned ts_word_cnt, unsigned tc_size);
|
PackMachBase(InputFile *, unsigned cpuid, unsigned t_flavor, unsigned ts_word_cnt, unsigned tc_size);
|
||||||
virtual ~PackMachBase();
|
virtual ~PackMachBase();
|
||||||
virtual int getVersion() const { return 13; }
|
virtual int getVersion() const { return 13; }
|
||||||
|
virtual const int *getCompressionMethods(int method, int level) const;
|
||||||
|
|
||||||
// called by the generic pack()
|
// called by the generic pack()
|
||||||
virtual void pack1(OutputFile *, Filter &); // generate executable header
|
virtual void pack1(OutputFile *, Filter &); // generate executable header
|
||||||
|
@ -347,6 +352,7 @@ protected:
|
||||||
|
|
||||||
static int __acc_cdecl_qsort compare_segment_command(void const *aa, void const *bb);
|
static int __acc_cdecl_qsort compare_segment_command(void const *aa, void const *bb);
|
||||||
|
|
||||||
|
unsigned my_cputype;
|
||||||
unsigned my_thread_flavor;
|
unsigned my_thread_flavor;
|
||||||
unsigned my_thread_state_word_count;
|
unsigned my_thread_state_word_count;
|
||||||
unsigned my_thread_command_size;
|
unsigned my_thread_command_size;
|
||||||
|
@ -369,7 +375,7 @@ class PackMachPPC32 : public PackMachBase<MachClass_BE32>
|
||||||
typedef PackMachBase<MachClass_BE32> super;
|
typedef PackMachBase<MachClass_BE32> super;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PackMachPPC32::PackMachPPC32(InputFile *f) : super(f,
|
PackMachPPC32::PackMachPPC32(InputFile *f) : super(f, Mach_header::CPU_TYPE_POWERPC,
|
||||||
Mach_thread_command::PPC_THREAD_STATE,
|
Mach_thread_command::PPC_THREAD_STATE,
|
||||||
sizeof(Mach_ppc_thread_state)>>2, sizeof(threado)) { }
|
sizeof(Mach_ppc_thread_state)>>2, sizeof(threado)) { }
|
||||||
|
|
||||||
|
@ -385,7 +391,6 @@ public:
|
||||||
virtual void set_native16(void *b, unsigned v) const { set_be16(b, v); }
|
virtual void set_native16(void *b, unsigned v) const { set_be16(b, v); }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
virtual const int *getCompressionMethods(int method, int level) const;
|
|
||||||
virtual const int *getFilters() const;
|
virtual const int *getFilters() const;
|
||||||
|
|
||||||
virtual void pack1_setup_threado(OutputFile *const fo);
|
virtual void pack1_setup_threado(OutputFile *const fo);
|
||||||
|
@ -412,7 +417,7 @@ class PackMachI386 : public PackMachBase<MachClass_LE32>
|
||||||
typedef PackMachBase<MachClass_LE32> super;
|
typedef PackMachBase<MachClass_LE32> super;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PackMachI386(InputFile *f) : super(f,
|
PackMachI386(InputFile *f) : super(f, Mach_header::CPU_TYPE_I386,
|
||||||
(unsigned)Mach_thread_command::i386_THREAD_STATE,
|
(unsigned)Mach_thread_command::i386_THREAD_STATE,
|
||||||
sizeof(Mach_i386_thread_state)>>2, sizeof(threado)) { }
|
sizeof(Mach_i386_thread_state)>>2, sizeof(threado)) { }
|
||||||
|
|
||||||
|
@ -420,14 +425,21 @@ public:
|
||||||
virtual const char *getName() const { return "Mach/i386"; }
|
virtual const char *getName() const { return "Mach/i386"; }
|
||||||
virtual const char *getFullName(const options_t *) const { return "i386-darwin.macho"; }
|
virtual const char *getFullName(const options_t *) const { return "i386-darwin.macho"; }
|
||||||
protected:
|
protected:
|
||||||
virtual const int *getCompressionMethods(int method, int level) const;
|
|
||||||
virtual const int *getFilters() const;
|
virtual const int *getFilters() const;
|
||||||
|
|
||||||
|
virtual acc_uint64l_t get_native64(const void *b) const { return get_be64(b); }
|
||||||
|
virtual unsigned get_native32(const void *b) const { return get_be32(b); }
|
||||||
|
virtual unsigned get_native16(const void *b) const { return get_be16(b); }
|
||||||
|
virtual void set_native64(void *b, acc_uint64l_t v) const { set_be64(b, v); }
|
||||||
|
virtual void set_native32(void *b, unsigned v) const { set_be32(b, v); }
|
||||||
|
virtual void set_native16(void *b, unsigned v) const { set_be16(b, v); }
|
||||||
|
|
||||||
virtual void pack1_setup_threado(OutputFile *const fo);
|
virtual void pack1_setup_threado(OutputFile *const fo);
|
||||||
virtual void pack3(OutputFile *, Filter &); // append loader
|
virtual void pack3(OutputFile *, Filter &); // append loader
|
||||||
virtual void pack4(OutputFile *, Filter &); // append PackHeader
|
virtual void pack4(OutputFile *, Filter &); // append PackHeader
|
||||||
virtual Linker* newLinker() const;
|
virtual Linker* newLinker() const;
|
||||||
virtual void buildLoader(const Filter *ft);
|
virtual void buildLoader(const Filter *ft);
|
||||||
|
virtual void addStubEntrySections(Filter const *);
|
||||||
|
|
||||||
struct Mach_thread_command
|
struct Mach_thread_command
|
||||||
{
|
{
|
||||||
|
|
|
@ -264,6 +264,8 @@ Packer* PackMaster::visitAllPackers(visit_func_t func, InputFile *f, const optio
|
||||||
// Mach (MacOS X PowerPC)
|
// Mach (MacOS X PowerPC)
|
||||||
if ((p = func(new PackMachPPC32(f), user)) != NULL)
|
if ((p = func(new PackMachPPC32(f), user)) != NULL)
|
||||||
return p;
|
return p;
|
||||||
|
if ((p = func(new PackMachI386(f), user)) != NULL)
|
||||||
|
return p;
|
||||||
|
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue
Block a user