547 lines
18 KiB
C++
547 lines
18 KiB
C++
//===--------------------------- DwarfParser.hpp --------------------------===//
|
|
//
|
|
// The LLVM Compiler Infrastructure
|
|
//
|
|
// This file is dual licensed under the MIT and the University of Illinois Open
|
|
// Source Licenses. See LICENSE.TXT for details.
|
|
//
|
|
//
|
|
// Parses DWARF CFIs (FDEs and CIEs).
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
|
|
#ifndef __DWARF_PARSER_HPP__
|
|
#define __DWARF_PARSER_HPP__
|
|
|
|
#include <cstdint>
|
|
#include <cstdlib>
|
|
|
|
#include "dwarf2.h"
|
|
#include "AddressSpace.hpp"
|
|
|
|
namespace _Unwind {
|
|
|
|
/// CFI_Parser does basic parsing of a CFI (Call Frame Information) records.
|
|
/// See Dwarf Spec for details:
|
|
/// http://refspecs.linuxbase.org/LSB_3.1.0/LSB-Core-generic/LSB-Core-generic/ehframechpt.html
|
|
///
|
|
template <typename A, typename R> class CFI_Parser {
|
|
public:
|
|
typedef typename A::pint_t pint_t;
|
|
|
|
/// Information encoded in a CIE (Common Information Entry)
|
|
struct CIE_Info {
|
|
pint_t cieStart;
|
|
pint_t cieLength;
|
|
pint_t cieInstructions;
|
|
pint_t personality;
|
|
uint32_t codeAlignFactor;
|
|
int dataAlignFactor;
|
|
uint8_t pointerEncoding;
|
|
uint8_t lsdaEncoding;
|
|
uint8_t personalityEncoding;
|
|
uint8_t personalityOffsetInCIE;
|
|
bool isSignalFrame;
|
|
bool fdesHaveAugmentationData;
|
|
uint8_t returnAddressRegister;
|
|
};
|
|
|
|
/// Information about an FDE (Frame Description Entry)
|
|
struct FDE_Info {
|
|
pint_t fdeStart;
|
|
pint_t fdeLength;
|
|
pint_t fdeInstructions;
|
|
pint_t pcStart;
|
|
pint_t pcEnd;
|
|
pint_t lsda;
|
|
};
|
|
|
|
/// Information about a frame layout and registers saved determined
|
|
/// by "running" the DWARF FDE "instructions"
|
|
enum {
|
|
kMaxRegisterNumber = R::LAST_REGISTER + 1
|
|
};
|
|
enum RegisterSavedWhere {
|
|
kRegisterUnused,
|
|
kRegisterInCFA,
|
|
kRegisterOffsetFromCFA,
|
|
kRegisterInRegister,
|
|
kRegisterAtExpression,
|
|
kRegisterIsExpression,
|
|
};
|
|
struct RegisterLocation {
|
|
RegisterSavedWhere location;
|
|
int64_t value;
|
|
};
|
|
struct PrologInfo {
|
|
uint32_t cfaRegister;
|
|
int32_t cfaRegisterOffset; // CFA = (cfaRegister)+cfaRegisterOffset
|
|
int64_t cfaExpression; // CFA = expression
|
|
uint32_t spExtraArgSize;
|
|
uint32_t codeOffsetAtStackDecrement;
|
|
RegisterLocation savedRegisters[kMaxRegisterNumber];
|
|
};
|
|
|
|
struct PrologInfoStackEntry {
|
|
PrologInfoStackEntry(PrologInfoStackEntry *n, const PrologInfo &i)
|
|
: next(n), info(i) {}
|
|
PrologInfoStackEntry *next;
|
|
PrologInfo info;
|
|
};
|
|
|
|
static void findPCRange(A &, pint_t, pint_t &, pint_t &);
|
|
|
|
static bool decodeFDE(A &, pint_t, FDE_Info *, CIE_Info *,
|
|
unw_proc_info_t *ctx);
|
|
static bool parseFDEInstructions(A &, const FDE_Info &, const CIE_Info &,
|
|
pint_t, PrologInfo *, unw_proc_info_t *ctx);
|
|
|
|
static bool parseCIE(A &, pint_t, CIE_Info *);
|
|
|
|
private:
|
|
static bool parseInstructions(A &, pint_t, pint_t, const CIE_Info &, pint_t,
|
|
PrologInfoStackEntry *&, PrologInfo *,
|
|
unw_proc_info_t *ctx);
|
|
};
|
|
|
|
///
|
|
/// Parse a FDE and return the last PC it covers.
|
|
///
|
|
template <typename A, typename R>
|
|
void CFI_Parser<A, R>::findPCRange(A &addressSpace, pint_t fde, pint_t &pcStart,
|
|
pint_t &pcEnd) {
|
|
pcStart = 0;
|
|
pcEnd = 0;
|
|
pint_t p = fde;
|
|
uint64_t cfiLength = addressSpace.get32(p);
|
|
p += 4;
|
|
if (cfiLength == 0xffffffff) {
|
|
// 0xffffffff means length is really the next 8 Bytes.
|
|
cfiLength = addressSpace.get64(p);
|
|
p += 8;
|
|
}
|
|
if (cfiLength == 0)
|
|
return;
|
|
uint32_t ciePointer = addressSpace.get32(p);
|
|
if (ciePointer == 0)
|
|
return;
|
|
pint_t nextCFI = p + cfiLength;
|
|
pint_t cieStart = p - ciePointer;
|
|
typename CFI_Parser<A, R>::CIE_Info cieInfo;
|
|
if (!parseCIE(addressSpace, cieStart, &cieInfo))
|
|
return;
|
|
p += 4;
|
|
// Parse pc begin and range.
|
|
pcStart = addressSpace.getEncodedP(p, nextCFI, cieInfo.pointerEncoding, NULL);
|
|
pcEnd = pcStart + addressSpace.getEncodedP(
|
|
p, nextCFI, cieInfo.pointerEncoding & 0x0F, NULL);
|
|
}
|
|
|
|
///
|
|
/// Parse a FDE into a CIE_Info and an FDE_Info
|
|
///
|
|
template <typename A, typename R>
|
|
bool CFI_Parser<A, R>::decodeFDE(A &addressSpace, pint_t fdeStart,
|
|
FDE_Info *fdeInfo, CIE_Info *cieInfo,
|
|
unw_proc_info_t *ctx) {
|
|
pint_t p = fdeStart;
|
|
uint64_t cfiLength = addressSpace.get32(p);
|
|
p += 4;
|
|
if (cfiLength == 0xffffffff) {
|
|
// 0xffffffff means length is really the next 8 Bytes.
|
|
cfiLength = addressSpace.get64(p);
|
|
p += 8;
|
|
}
|
|
if (cfiLength == 0)
|
|
return false;
|
|
uint32_t ciePointer = addressSpace.get32(p);
|
|
if (ciePointer == 0)
|
|
return false;
|
|
pint_t nextCFI = p + cfiLength;
|
|
pint_t cieStart = p - ciePointer;
|
|
if (!parseCIE(addressSpace, cieStart, cieInfo))
|
|
return false;
|
|
p += 4;
|
|
// Parse pc begin and range.
|
|
pint_t pcStart =
|
|
addressSpace.getEncodedP(p, nextCFI, cieInfo->pointerEncoding, ctx);
|
|
pint_t pcRange = addressSpace.getEncodedP(
|
|
p, nextCFI, cieInfo->pointerEncoding & 0x0F, ctx);
|
|
// Parse rest of info.
|
|
fdeInfo->lsda = 0;
|
|
// Check for augmentation length
|
|
if (cieInfo->fdesHaveAugmentationData) {
|
|
uintptr_t augLen = addressSpace.getULEB128(p, nextCFI);
|
|
pint_t endOfAug = p + augLen;
|
|
if (cieInfo->lsdaEncoding != DW_EH_PE_omit) {
|
|
// Peek at value (without indirection). Zero means no LSDA.
|
|
pint_t lsdaStart = p;
|
|
if (addressSpace.getEncodedP(p, nextCFI, cieInfo->lsdaEncoding & 0x0F,
|
|
ctx) != 0) {
|
|
// Reset pointer and re-parse LSDA address.
|
|
p = lsdaStart;
|
|
fdeInfo->lsda =
|
|
addressSpace.getEncodedP(p, nextCFI, cieInfo->lsdaEncoding, ctx);
|
|
}
|
|
}
|
|
p = endOfAug;
|
|
}
|
|
fdeInfo->fdeStart = fdeStart;
|
|
fdeInfo->fdeLength = nextCFI - fdeStart;
|
|
fdeInfo->fdeInstructions = p;
|
|
fdeInfo->pcStart = pcStart;
|
|
fdeInfo->pcEnd = pcStart + pcRange;
|
|
return true;
|
|
}
|
|
|
|
/// Extract info from a CIE
|
|
template <typename A, typename R>
|
|
bool CFI_Parser<A, R>::parseCIE(A &addressSpace, pint_t cie,
|
|
CIE_Info *cieInfo) {
|
|
cieInfo->pointerEncoding = 0;
|
|
cieInfo->lsdaEncoding = DW_EH_PE_omit;
|
|
cieInfo->personalityEncoding = 0;
|
|
cieInfo->personalityOffsetInCIE = 0;
|
|
cieInfo->personality = 0;
|
|
cieInfo->codeAlignFactor = 0;
|
|
cieInfo->dataAlignFactor = 0;
|
|
cieInfo->isSignalFrame = false;
|
|
cieInfo->fdesHaveAugmentationData = false;
|
|
cieInfo->cieStart = cie;
|
|
pint_t p = cie;
|
|
uint64_t cieLength = addressSpace.get32(p);
|
|
p += 4;
|
|
pint_t cieContentEnd = p + cieLength;
|
|
if (cieLength == 0xffffffff) {
|
|
// 0xffffffff means length is really the next 8 Bytes.
|
|
cieLength = addressSpace.get64(p);
|
|
p += 8;
|
|
cieContentEnd = p + cieLength;
|
|
}
|
|
if (cieLength == 0)
|
|
return true;
|
|
// CIE ID is always 0
|
|
if (addressSpace.get32(p) != 0)
|
|
return false;
|
|
p += 4;
|
|
// Version is always 1 or 3
|
|
uint8_t version = addressSpace.get8(p);
|
|
if (version != 1 && version != 3)
|
|
return false;
|
|
++p;
|
|
// Save start of augmentation string and find end.
|
|
pint_t strStart = p;
|
|
while (addressSpace.get8(p) != 0)
|
|
++p;
|
|
++p;
|
|
// Parse code alignment factor
|
|
cieInfo->codeAlignFactor = addressSpace.getULEB128(p, cieContentEnd);
|
|
// Parse data alignment factor
|
|
cieInfo->dataAlignFactor = addressSpace.getSLEB128(p, cieContentEnd);
|
|
// Parse return address register
|
|
cieInfo->returnAddressRegister = R::dwarf2regno((uint8_t)addressSpace.getULEB128(p, cieContentEnd));
|
|
// Parse augmentation data based on augmentation string.
|
|
if (addressSpace.get8(strStart) == 'z') {
|
|
// parse augmentation data length
|
|
addressSpace.getULEB128(p, cieContentEnd);
|
|
for (pint_t s = strStart; addressSpace.get8(s) != '\0'; ++s) {
|
|
switch (addressSpace.get8(s)) {
|
|
case 'z':
|
|
cieInfo->fdesHaveAugmentationData = true;
|
|
break;
|
|
case 'P':
|
|
cieInfo->personalityEncoding = addressSpace.get8(p);
|
|
++p;
|
|
cieInfo->personalityOffsetInCIE = p - cie;
|
|
cieInfo->personality = addressSpace.getEncodedP(
|
|
p, cieContentEnd, cieInfo->personalityEncoding, NULL);
|
|
break;
|
|
case 'L':
|
|
cieInfo->lsdaEncoding = addressSpace.get8(p);
|
|
++p;
|
|
break;
|
|
case 'R':
|
|
cieInfo->pointerEncoding = addressSpace.get8(p);
|
|
++p;
|
|
break;
|
|
case 'S':
|
|
cieInfo->isSignalFrame = true;
|
|
break;
|
|
default:
|
|
// ignore unknown letters
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
cieInfo->cieLength = cieContentEnd - cieInfo->cieStart;
|
|
cieInfo->cieInstructions = p;
|
|
return true;
|
|
}
|
|
|
|
/// "Run" the dwarf instructions and create the abstact PrologInfo for an FDE.
|
|
template <typename A, typename R>
|
|
bool CFI_Parser<A, R>::parseFDEInstructions(A &addressSpace,
|
|
const FDE_Info &fdeInfo,
|
|
const CIE_Info &cieInfo,
|
|
pint_t upToPC, PrologInfo *results,
|
|
unw_proc_info_t *ctx) {
|
|
// Clear results.
|
|
memset(results, 0, sizeof(*results));
|
|
PrologInfoStackEntry *rememberStack = NULL;
|
|
|
|
// First parse the CIE then FDE instructions.
|
|
if (!parseInstructions(addressSpace, cieInfo.cieInstructions,
|
|
cieInfo.cieStart + cieInfo.cieLength, cieInfo,
|
|
(pint_t)(-1), rememberStack, results, ctx))
|
|
return false;
|
|
return parseInstructions(addressSpace, fdeInfo.fdeInstructions,
|
|
fdeInfo.fdeStart + fdeInfo.fdeLength, cieInfo,
|
|
upToPC - fdeInfo.pcStart, rememberStack, results,
|
|
ctx);
|
|
}
|
|
|
|
/// "Run" the DWARF instructions.
|
|
template <typename A, typename R>
|
|
bool
|
|
CFI_Parser<A, R>::parseInstructions(A &addressSpace, pint_t instructions,
|
|
pint_t instructionsEnd,
|
|
const CIE_Info &cieInfo, pint_t pcoffset,
|
|
PrologInfoStackEntry *&rememberStack,
|
|
PrologInfo *results, unw_proc_info_t *ctx) {
|
|
pint_t p = instructions;
|
|
uint32_t codeOffset = 0;
|
|
PrologInfo initialState = *results;
|
|
|
|
// See Dwarf Spec, section 6.4.2 for details on unwind opcodes.
|
|
while (p < instructionsEnd && codeOffset < pcoffset) {
|
|
uint64_t reg;
|
|
uint64_t reg2;
|
|
int64_t offset;
|
|
uint64_t length;
|
|
uint8_t opcode = addressSpace.get8(p);
|
|
uint8_t operand;
|
|
PrologInfoStackEntry *entry;
|
|
++p;
|
|
switch (opcode) {
|
|
case DW_CFA_nop:
|
|
break;
|
|
case DW_CFA_set_loc:
|
|
codeOffset = addressSpace.getEncodedP(p, instructionsEnd,
|
|
cieInfo.pointerEncoding, ctx);
|
|
break;
|
|
case DW_CFA_advance_loc1:
|
|
codeOffset += (addressSpace.get8(p) * cieInfo.codeAlignFactor);
|
|
p += 1;
|
|
break;
|
|
case DW_CFA_advance_loc2:
|
|
codeOffset += (addressSpace.get16(p) * cieInfo.codeAlignFactor);
|
|
p += 2;
|
|
break;
|
|
case DW_CFA_advance_loc4:
|
|
codeOffset += (addressSpace.get32(p) * cieInfo.codeAlignFactor);
|
|
p += 4;
|
|
break;
|
|
case DW_CFA_offset_extended:
|
|
reg = R::dwarf2regno(addressSpace.getULEB128(p, instructionsEnd));
|
|
offset =
|
|
addressSpace.getULEB128(p, instructionsEnd) * cieInfo.dataAlignFactor;
|
|
if (reg > kMaxRegisterNumber)
|
|
return false;
|
|
results->savedRegisters[reg].location = kRegisterInCFA;
|
|
results->savedRegisters[reg].value = offset;
|
|
break;
|
|
case DW_CFA_restore_extended:
|
|
reg = R::dwarf2regno(addressSpace.getULEB128(p, instructionsEnd));
|
|
if (reg > kMaxRegisterNumber)
|
|
return false;
|
|
results->savedRegisters[reg] = initialState.savedRegisters[reg];
|
|
break;
|
|
case DW_CFA_undefined:
|
|
reg = R::dwarf2regno(addressSpace.getULEB128(p, instructionsEnd));
|
|
if (reg > kMaxRegisterNumber)
|
|
return false;
|
|
results->savedRegisters[reg].location = kRegisterUnused;
|
|
break;
|
|
case DW_CFA_same_value:
|
|
reg = R::dwarf2regno(addressSpace.getULEB128(p, instructionsEnd));
|
|
if (reg > kMaxRegisterNumber)
|
|
return false;
|
|
// "same value" means register was stored in frame, but its current
|
|
// value has not changed, so no need to restore from frame.
|
|
// We model this as if the register was never saved.
|
|
results->savedRegisters[reg].location = kRegisterUnused;
|
|
break;
|
|
case DW_CFA_register:
|
|
reg = R::dwarf2regno(addressSpace.getULEB128(p, instructionsEnd));
|
|
reg2 = R::dwarf2regno(addressSpace.getULEB128(p, instructionsEnd));
|
|
if (reg > kMaxRegisterNumber)
|
|
return false;
|
|
if (reg2 > kMaxRegisterNumber)
|
|
return false;
|
|
results->savedRegisters[reg].location = kRegisterInRegister;
|
|
results->savedRegisters[reg].value = reg2;
|
|
break;
|
|
case DW_CFA_remember_state:
|
|
// avoid operator new, because that would be an upward dependency
|
|
entry = (PrologInfoStackEntry *)malloc(sizeof(PrologInfoStackEntry));
|
|
if (entry == NULL)
|
|
return false;
|
|
|
|
entry->next = rememberStack;
|
|
entry->info = *results;
|
|
rememberStack = entry;
|
|
break;
|
|
case DW_CFA_restore_state:
|
|
if (rememberStack == NULL)
|
|
return false;
|
|
{
|
|
PrologInfoStackEntry *top = rememberStack;
|
|
*results = top->info;
|
|
rememberStack = top->next;
|
|
free((char *)top);
|
|
}
|
|
break;
|
|
case DW_CFA_def_cfa:
|
|
reg = R::dwarf2regno(addressSpace.getULEB128(p, instructionsEnd));
|
|
offset = addressSpace.getULEB128(p, instructionsEnd);
|
|
if (reg > kMaxRegisterNumber)
|
|
return false;
|
|
results->cfaRegister = reg;
|
|
results->cfaRegisterOffset = offset;
|
|
break;
|
|
case DW_CFA_def_cfa_register:
|
|
reg = R::dwarf2regno(addressSpace.getULEB128(p, instructionsEnd));
|
|
if (reg > kMaxRegisterNumber)
|
|
return false;
|
|
results->cfaRegister = reg;
|
|
break;
|
|
case DW_CFA_def_cfa_offset:
|
|
results->cfaRegisterOffset = addressSpace.getULEB128(p, instructionsEnd);
|
|
results->codeOffsetAtStackDecrement = codeOffset;
|
|
break;
|
|
case DW_CFA_def_cfa_expression:
|
|
results->cfaRegister = 0;
|
|
results->cfaExpression = p;
|
|
length = addressSpace.getULEB128(p, instructionsEnd);
|
|
p += length;
|
|
break;
|
|
case DW_CFA_expression:
|
|
reg = R::dwarf2regno(addressSpace.getULEB128(p, instructionsEnd));
|
|
if (reg > kMaxRegisterNumber)
|
|
return false;
|
|
results->savedRegisters[reg].location = kRegisterAtExpression;
|
|
results->savedRegisters[reg].value = p;
|
|
length = addressSpace.getULEB128(p, instructionsEnd);
|
|
p += length;
|
|
break;
|
|
case DW_CFA_offset_extended_sf:
|
|
reg = R::dwarf2regno(addressSpace.getULEB128(p, instructionsEnd));
|
|
if (reg > kMaxRegisterNumber)
|
|
return false;
|
|
offset =
|
|
addressSpace.getSLEB128(p, instructionsEnd) * cieInfo.dataAlignFactor;
|
|
results->savedRegisters[reg].location = kRegisterInCFA;
|
|
results->savedRegisters[reg].value = offset;
|
|
break;
|
|
case DW_CFA_def_cfa_sf:
|
|
reg = R::dwarf2regno(addressSpace.getULEB128(p, instructionsEnd));
|
|
offset =
|
|
addressSpace.getSLEB128(p, instructionsEnd) * cieInfo.dataAlignFactor;
|
|
if (reg > kMaxRegisterNumber)
|
|
return false;
|
|
results->cfaRegister = reg;
|
|
results->cfaRegisterOffset = offset;
|
|
break;
|
|
case DW_CFA_def_cfa_offset_sf:
|
|
results->cfaRegisterOffset =
|
|
addressSpace.getSLEB128(p, instructionsEnd) * cieInfo.dataAlignFactor;
|
|
results->codeOffsetAtStackDecrement = codeOffset;
|
|
break;
|
|
case DW_CFA_val_offset:
|
|
reg = R::dwarf2regno(addressSpace.getULEB128(p, instructionsEnd));
|
|
offset =
|
|
addressSpace.getULEB128(p, instructionsEnd) * cieInfo.dataAlignFactor;
|
|
if (reg > kMaxRegisterNumber)
|
|
return false;
|
|
results->savedRegisters[reg].location = kRegisterOffsetFromCFA;
|
|
results->savedRegisters[reg].value = offset;
|
|
break;
|
|
case DW_CFA_val_offset_sf:
|
|
reg = R::dwarf2regno(addressSpace.getULEB128(p, instructionsEnd));
|
|
if (reg > kMaxRegisterNumber)
|
|
return false;
|
|
offset =
|
|
addressSpace.getSLEB128(p, instructionsEnd) * cieInfo.dataAlignFactor;
|
|
results->savedRegisters[reg].location = kRegisterOffsetFromCFA;
|
|
results->savedRegisters[reg].value = offset;
|
|
break;
|
|
case DW_CFA_val_expression:
|
|
reg = R::dwarf2regno(addressSpace.getULEB128(p, instructionsEnd));
|
|
if (reg > kMaxRegisterNumber)
|
|
return false;
|
|
results->savedRegisters[reg].location = kRegisterIsExpression;
|
|
results->savedRegisters[reg].value = p;
|
|
length = addressSpace.getULEB128(p, instructionsEnd);
|
|
p += length;
|
|
break;
|
|
case DW_CFA_GNU_window_save:
|
|
#if defined(__sparc__)
|
|
for (reg = 8; reg < 16; ++reg) {
|
|
results->savedRegisters[reg].location = kRegisterInRegister;
|
|
results->savedRegisters[reg].value = reg + 16;
|
|
}
|
|
for (reg = 16; reg < 32; ++reg) {
|
|
results->savedRegisters[reg].location = kRegisterInCFA;
|
|
results->savedRegisters[reg].value = (reg - 16) * sizeof(typename R::reg_t);
|
|
}
|
|
break;
|
|
#else
|
|
return false;
|
|
#endif
|
|
case DW_CFA_GNU_args_size:
|
|
offset = addressSpace.getULEB128(p, instructionsEnd);
|
|
results->spExtraArgSize = offset;
|
|
break;
|
|
case DW_CFA_GNU_negative_offset_extended:
|
|
reg = R::dwarf2regno(addressSpace.getULEB128(p, instructionsEnd));
|
|
if (reg > kMaxRegisterNumber)
|
|
return false;
|
|
offset =
|
|
addressSpace.getULEB128(p, instructionsEnd) * cieInfo.dataAlignFactor;
|
|
results->savedRegisters[reg].location = kRegisterInCFA;
|
|
results->savedRegisters[reg].value = -offset;
|
|
break;
|
|
default:
|
|
operand = opcode & 0x3F;
|
|
switch (opcode & 0xC0) {
|
|
case DW_CFA_offset:
|
|
reg = R::dwarf2regno(operand);
|
|
if (reg > kMaxRegisterNumber)
|
|
return false;
|
|
offset = addressSpace.getULEB128(p, instructionsEnd) *
|
|
cieInfo.dataAlignFactor;
|
|
results->savedRegisters[reg].location = kRegisterInCFA;
|
|
results->savedRegisters[reg].value = offset;
|
|
break;
|
|
case DW_CFA_advance_loc:
|
|
codeOffset += operand * cieInfo.codeAlignFactor;
|
|
break;
|
|
case DW_CFA_restore:
|
|
reg = R::dwarf2regno(operand);
|
|
if (reg > kMaxRegisterNumber)
|
|
return false;
|
|
results->savedRegisters[reg] = initialState.savedRegisters[reg];
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
} // namespace _Unwind
|
|
|
|
#endif // __DWARF_PARSER_HPP__
|