summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjyao1 <jyao1@6f19259b-4bc3-4df7-8a09-765794883524>2011-11-23 13:23:34 +0000
committerjyao1 <jyao1@6f19259b-4bc3-4df7-8a09-765794883524>2011-11-23 13:23:34 +0000
commit21d13c61561c465a4d4cfe0f0d188765fc8ae3b9 (patch)
tree4ae1706483cdf855dc2e6a169a45a70068bb375a
parent6cf9230ff23d7b856692b86da5ceea20183141a2 (diff)
downloadedk2-platforms-21d13c61561c465a4d4cfe0f0d188765fc8ae3b9.tar.xz
Re-org the EBC thunk code for better code maintenance.
signed-off-by: jyao1 reviewed-by: lgao4 git-svn-id: https://edk2.svn.sourceforge.net/svnroot/edk2/trunk/edk2@12769 6f19259b-4bc3-4df7-8a09-765794883524
-rw-r--r--MdeModulePkg/Universal/EbcDxe/Ia32/EbcSupport.c184
-rw-r--r--MdeModulePkg/Universal/EbcDxe/X64/EbcSupport.c216
2 files changed, 161 insertions, 239 deletions
diff --git a/MdeModulePkg/Universal/EbcDxe/Ia32/EbcSupport.c b/MdeModulePkg/Universal/EbcDxe/Ia32/EbcSupport.c
index da1ad2cd5d..41dc3a8585 100644
--- a/MdeModulePkg/Universal/EbcDxe/Ia32/EbcSupport.c
+++ b/MdeModulePkg/Universal/EbcDxe/Ia32/EbcSupport.c
@@ -1,6 +1,6 @@
/** @file
This module contains EBC support routines that are customized based on
- the target processor.
+ the target ia32 processor.
Copyright (c) 2006 - 2011, Intel Corporation. All rights reserved.<BR>
This program and the accompanying materials
@@ -23,10 +23,47 @@ WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
// platform-specific configurations.
//
#define VM_STACK_SIZE (1024 * 4)
-#define EBC_THUNK_SIZE 32
#define STACK_REMAIN_SIZE (1024 * 4)
+//
+// This is instruction buffer used to create EBC thunk
+//
+#define EBC_ENTRYPOINT_SIGNATURE 0xAFAFAFAF
+#define EBC_LL_EBC_ENTRYPOINT_SIGNATURE 0xFAFAFAFA
+UINT8 mInstructionBufferTemplate[] = {
+ //
+ // Add a magic code here to help the VM recognize the thunk..
+ // mov eax, 0xca112ebc => B8 BC 2E 11 CA
+ //
+ 0xB8, 0xBC, 0x2E, 0x11, 0xCA,
+ //
+ // Add code bytes to load up a processor register with the EBC entry point.
+ // mov eax, EbcEntryPoint => B8 XX XX XX XX (To be fixed at runtime)
+ // These 4 bytes of the thunk entry is the address of the EBC
+ // entry point.
+ //
+ 0xB8,
+ (UINT8)(EBC_ENTRYPOINT_SIGNATURE & 0xFF),
+ (UINT8)((EBC_ENTRYPOINT_SIGNATURE >> 8) & 0xFF),
+ (UINT8)((EBC_ENTRYPOINT_SIGNATURE >> 16) & 0xFF),
+ (UINT8)((EBC_ENTRYPOINT_SIGNATURE >> 24) & 0xFF),
+ //
+ // Stick in a load of ecx with the address of appropriate VM function.
+ // mov ecx, EbcLLEbcInterpret => B9 XX XX XX XX (To be fixed at runtime)
+ //
+ 0xB9,
+ (UINT8)(EBC_LL_EBC_ENTRYPOINT_SIGNATURE & 0xFF),
+ (UINT8)((EBC_LL_EBC_ENTRYPOINT_SIGNATURE >> 8) & 0xFF),
+ (UINT8)((EBC_LL_EBC_ENTRYPOINT_SIGNATURE >> 16) & 0xFF),
+ (UINT8)((EBC_LL_EBC_ENTRYPOINT_SIGNATURE >> 24) & 0xFF),
+ //
+ // Stick in jump opcode bytes
+ // jmp ecx => FF E1
+ //
+ 0xFF, 0xE1,
+};
+
/**
Begin executing an EBC image.
This is used for Ebc Thunk call.
@@ -79,54 +116,37 @@ EbcLLCALLEX (
{
UINTN IsThunk;
UINTN TargetEbcAddr;
+ UINT8 InstructionBuffer[sizeof(mInstructionBufferTemplate)];
+ UINTN Index;
+ UINTN IndexOfEbcEntrypoint;
IsThunk = 1;
TargetEbcAddr = 0;
+ IndexOfEbcEntrypoint = 0;
//
// Processor specific code to check whether the callee is a thunk to EBC.
//
- if (*((UINT8 *)FuncAddr) != 0xB8) {
- IsThunk = 0;
- goto Action;
- }
- if (*((UINT8 *)FuncAddr + 1) != 0xBC) {
- IsThunk = 0;
- goto Action;
- }
- if (*((UINT8 *)FuncAddr + 2) != 0x2E) {
- IsThunk = 0;
- goto Action;
- }
- if (*((UINT8 *)FuncAddr + 3) != 0x11) {
- IsThunk = 0;
- goto Action;
- }
- if (*((UINT8 *)FuncAddr + 4) != 0xCA) {
- IsThunk = 0;
- goto Action;
- }
- if (*((UINT8 *)FuncAddr + 5) != 0xB8) {
- IsThunk = 0;
- goto Action;
- }
- if (*((UINT8 *)FuncAddr + 10) != 0xB9) {
- IsThunk = 0;
- goto Action;
- }
- if (*((UINT8 *)FuncAddr + 15) != 0xFF) {
- IsThunk = 0;
- goto Action;
+ CopyMem (InstructionBuffer, (VOID *)FuncAddr, sizeof(InstructionBuffer));
+ //
+ // Fill the signature according to mInstructionBufferTemplate
+ //
+ for (Index = 0; Index < sizeof(mInstructionBufferTemplate) - sizeof(UINTN); Index++) {
+ if (*(UINTN *)&mInstructionBufferTemplate[Index] == EBC_ENTRYPOINT_SIGNATURE) {
+ *(UINTN *)&InstructionBuffer[Index] = EBC_ENTRYPOINT_SIGNATURE;
+ IndexOfEbcEntrypoint = Index;
+ }
+ if (*(UINTN *)&mInstructionBufferTemplate[Index] == EBC_LL_EBC_ENTRYPOINT_SIGNATURE) {
+ *(UINTN *)&InstructionBuffer[Index] = EBC_LL_EBC_ENTRYPOINT_SIGNATURE;
+ }
}
- if (*((UINT8 *)FuncAddr + 16) != 0xE1) {
+ //
+ // Check if we need thunk to native
+ //
+ if (CompareMem (InstructionBuffer, mInstructionBufferTemplate, sizeof(mInstructionBufferTemplate)) != 0) {
IsThunk = 0;
- goto Action;
}
- TargetEbcAddr = ((UINTN)(*((UINT8 *)FuncAddr + 9)) << 24) + ((UINTN)(*((UINT8 *)FuncAddr + 8)) << 16) +
- ((UINTN)(*((UINT8 *)FuncAddr + 7)) << 8) + ((UINTN)(*((UINT8 *)FuncAddr + 6)));
-
-Action:
if (IsThunk == 1){
//
// The callee is a thunk to EBC, adjust the stack pointer down 16 bytes and
@@ -139,11 +159,12 @@ Action:
VmPtr->Gpr[0] -= 8;
VmWriteMem64 (VmPtr, (UINTN) VmPtr->Gpr[0], (UINT64) (UINTN) (VmPtr->Ip + Size));
+ CopyMem (&TargetEbcAddr, (UINT8 *)FuncAddr + IndexOfEbcEntrypoint, sizeof(UINTN));
VmPtr->Ip = (VMIP) (UINTN) TargetEbcAddr;
} else {
//
// The callee is not a thunk to EBC, call native code,
- // and get return value
+ // and get return value.
//
VmPtr->Gpr[7] = EbcLLCALLEXNative (FuncAddr, NewStackPointer, FramePtr);
@@ -449,8 +470,6 @@ EbcCreateThunks (
UINT8 *Ptr;
UINT8 *ThunkBase;
UINT32 Index;
- UINT32 Addr;
- INT32 Size;
INT32 ThunkSize;
//
@@ -460,10 +479,9 @@ EbcCreateThunks (
return EFI_INVALID_PARAMETER;
}
- Size = EBC_THUNK_SIZE;
- ThunkSize = Size;
+ ThunkSize = sizeof(mInstructionBufferTemplate);
- Ptr = AllocatePool (Size);
+ Ptr = AllocatePool (sizeof(mInstructionBufferTemplate));
if (Ptr == NULL) {
return EFI_OUT_OF_RESOURCES;
@@ -481,75 +499,27 @@ EbcCreateThunks (
*Thunk = (VOID *) Ptr;
//
- // Add a magic code here to help the VM recognize the thunk..
- // mov eax, 0xca112ebc => B8 BC 2E 11 CA
+ // Copy whole thunk instruction buffer template
//
- *Ptr = 0xB8;
- Ptr++;
- Size--;
- Addr = (UINT32) 0xCA112EBC;
- for (Index = 0; Index < sizeof (Addr); Index++) {
- *Ptr = (UINT8) (UINTN) Addr;
- Addr >>= 8;
- Ptr++;
- Size--;
- }
+ CopyMem (Ptr, mInstructionBufferTemplate, sizeof(mInstructionBufferTemplate));
//
- // Add code bytes to load up a processor register with the EBC entry point.
- // mov eax, 0xaa55aa55 => B8 55 AA 55 AA
- // The first 8 bytes of the thunk entry is the address of the EBC
- // entry point.
+ // Patch EbcEntryPoint and EbcLLEbcInterpret
//
- *Ptr = 0xB8;
- Ptr++;
- Size--;
- Addr = (UINT32) EbcEntryPoint;
- for (Index = 0; Index < sizeof (Addr); Index++) {
- *Ptr = (UINT8) (UINTN) Addr;
- Addr >>= 8;
- Ptr++;
- Size--;
- }
- //
- // Stick in a load of ecx with the address of appropriate VM function.
- // mov ecx 12345678h => 0xB9 0x78 0x56 0x34 0x12
- //
- if ((Flags & FLAG_THUNK_ENTRY_POINT) != 0) {
- Addr = (UINT32) (UINTN) EbcLLExecuteEbcImageEntryPoint;
- } else {
- Addr = (UINT32) (UINTN) EbcLLEbcInterpret;
+ for (Index = 0; Index < sizeof(mInstructionBufferTemplate) - sizeof(UINTN); Index++) {
+ if (*(UINTN *)&Ptr[Index] == EBC_ENTRYPOINT_SIGNATURE) {
+ *(UINTN *)&Ptr[Index] = (UINTN)EbcEntryPoint;
+ }
+ if (*(UINTN *)&Ptr[Index] == EBC_LL_EBC_ENTRYPOINT_SIGNATURE) {
+ if ((Flags & FLAG_THUNK_ENTRY_POINT) != 0) {
+ *(UINTN *)&Ptr[Index] = (UINTN)EbcLLExecuteEbcImageEntryPoint;
+ } else {
+ *(UINTN *)&Ptr[Index] = (UINTN)EbcLLEbcInterpret;
+ }
+ }
}
//
- // MOV ecx
- //
- *Ptr = 0xB9;
- Ptr++;
- Size--;
- for (Index = 0; Index < sizeof (Addr); Index++) {
- *Ptr = (UINT8) Addr;
- Addr >>= 8;
- Ptr++;
- Size--;
- }
- //
- // Stick in jump opcode bytes for jmp ecx => 0xFF 0xE1
- //
- *Ptr = 0xFF;
- Ptr++;
- Size--;
- *Ptr = 0xE1;
- Size--;
-
- //
- // Double check that our defined size is ok (application error)
- //
- if (Size < 0) {
- ASSERT (FALSE);
- return EFI_BUFFER_TOO_SMALL;
- }
- //
// Add the thunk to the list for this image. Do this last since the add
// function flushes the cache for us.
//
diff --git a/MdeModulePkg/Universal/EbcDxe/X64/EbcSupport.c b/MdeModulePkg/Universal/EbcDxe/X64/EbcSupport.c
index a31422b62a..3f99a1a6e2 100644
--- a/MdeModulePkg/Universal/EbcDxe/X64/EbcSupport.c
+++ b/MdeModulePkg/Universal/EbcDxe/X64/EbcSupport.c
@@ -23,10 +23,55 @@ WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
// platform-specific configurations.
//
#define VM_STACK_SIZE (1024 * 8)
-#define EBC_THUNK_SIZE 64
#define STACK_REMAIN_SIZE (1024 * 4)
+//
+// This is instruction buffer used to create EBC thunk
+//
+#define EBC_ENTRYPOINT_SIGNATURE 0xAFAFAFAFAFAFAFAFull
+#define EBC_LL_EBC_ENTRYPOINT_SIGNATURE 0xFAFAFAFAFAFAFAFAull
+UINT8 mInstructionBufferTemplate[] = {
+ //
+ // Add a magic code here to help the VM recognize the thunk..
+ // mov rax, 0xca112ebcca112ebc => 48 B8 BC 2E 11 CA BC 2E 11 CA
+ //
+ 0x48, 0xB8, 0xBC, 0x2E, 0x11, 0xCA, 0xBC, 0x2E, 0x11, 0xCA,
+ //
+ // Add code bytes to load up a processor register with the EBC entry point.
+ // mov r10, EbcEntryPoint => 49 BA XX XX XX XX XX XX XX XX (To be fixed at runtime)
+ // These 8 bytes of the thunk entry is the address of the EBC
+ // entry point.
+ //
+ 0x49, 0xBA,
+ (UINT8)(EBC_ENTRYPOINT_SIGNATURE & 0xFF),
+ (UINT8)((EBC_ENTRYPOINT_SIGNATURE >> 8) & 0xFF),
+ (UINT8)((EBC_ENTRYPOINT_SIGNATURE >> 16) & 0xFF),
+ (UINT8)((EBC_ENTRYPOINT_SIGNATURE >> 24) & 0xFF),
+ (UINT8)((EBC_ENTRYPOINT_SIGNATURE >> 32) & 0xFF),
+ (UINT8)((EBC_ENTRYPOINT_SIGNATURE >> 40) & 0xFF),
+ (UINT8)((EBC_ENTRYPOINT_SIGNATURE >> 48) & 0xFF),
+ (UINT8)((EBC_ENTRYPOINT_SIGNATURE >> 56) & 0xFF),
+ //
+ // Stick in a load of r11 with the address of appropriate VM function.
+ // mov r11, EbcLLEbcInterpret => 49 BB XX XX XX XX XX XX XX XX (To be fixed at runtime)
+ //
+ 0x49, 0xBB,
+ (UINT8)(EBC_LL_EBC_ENTRYPOINT_SIGNATURE & 0xFF),
+ (UINT8)((EBC_LL_EBC_ENTRYPOINT_SIGNATURE >> 8) & 0xFF),
+ (UINT8)((EBC_LL_EBC_ENTRYPOINT_SIGNATURE >> 16) & 0xFF),
+ (UINT8)((EBC_LL_EBC_ENTRYPOINT_SIGNATURE >> 24) & 0xFF),
+ (UINT8)((EBC_LL_EBC_ENTRYPOINT_SIGNATURE >> 32) & 0xFF),
+ (UINT8)((EBC_LL_EBC_ENTRYPOINT_SIGNATURE >> 40) & 0xFF),
+ (UINT8)((EBC_LL_EBC_ENTRYPOINT_SIGNATURE >> 48) & 0xFF),
+ (UINT8)((EBC_LL_EBC_ENTRYPOINT_SIGNATURE >> 56) & 0xFF),
+ //
+ // Stick in jump opcode bytes
+ // jmp r11 => 41 FF E3
+ //
+ 0x41, 0xFF, 0xE3,
+};
+
/**
Begin executing an EBC image.
This is used for Ebc Thunk call.
@@ -382,8 +427,6 @@ EbcCreateThunks (
UINT8 *Ptr;
UINT8 *ThunkBase;
UINT32 Index;
- UINT64 Addr;
- INT32 Size;
INT32 ThunkSize;
//
@@ -393,10 +436,9 @@ EbcCreateThunks (
return EFI_INVALID_PARAMETER;
}
- Size = EBC_THUNK_SIZE;
- ThunkSize = Size;
+ ThunkSize = sizeof(mInstructionBufferTemplate);
- Ptr = AllocatePool (Size);
+ Ptr = AllocatePool (sizeof(mInstructionBufferTemplate));
if (Ptr == NULL) {
return EFI_OUT_OF_RESOURCES;
@@ -414,90 +456,27 @@ EbcCreateThunks (
*Thunk = (VOID *) Ptr;
//
- // Add a magic code here to help the VM recognize the thunk..
- // mov rax, ca112ebccall2ebch => 48 B8 BC 2E 11 CA BC 2E 11 CA
- //
- *Ptr = 0x48;
- Ptr++;
- Size--;
- *Ptr = 0xB8;
- Ptr++;
- Size--;
- Addr = (UINT64) 0xCA112EBCCA112EBCULL;
- for (Index = 0; Index < sizeof (Addr); Index++) {
- *Ptr = (UINT8) (UINTN) Addr;
- Addr >>= 8;
- Ptr++;
- Size--;
- }
-
+ // Copy whole thunk instruction buffer template
//
- // Add code bytes to load up a processor register with the EBC entry point.
- // mov r10, 123456789abcdef0h => 49 BA F0 DE BC 9A 78 56 34 12
- // The first 8 bytes of the thunk entry is the address of the EBC
- // entry point.
- //
- *Ptr = 0x49;
- Ptr++;
- Size--;
- *Ptr = 0xBA;
- Ptr++;
- Size--;
- Addr = (UINT64) EbcEntryPoint;
- for (Index = 0; Index < sizeof (Addr); Index++) {
- *Ptr = (UINT8) (UINTN) Addr;
- Addr >>= 8;
- Ptr++;
- Size--;
- }
+ CopyMem (Ptr, mInstructionBufferTemplate, sizeof(mInstructionBufferTemplate));
//
- // Stick in a load of ecx with the address of appropriate VM function.
- // Using r11 because it's a volatile register and won't be used in this
- // point.
- // mov r11 123456789abcdef0h => 49 BB F0 DE BC 9A 78 56 34 12
+ // Patch EbcEntryPoint and EbcLLEbcInterpret
//
- if ((Flags & FLAG_THUNK_ENTRY_POINT) != 0) {
- Addr = (UINTN) EbcLLExecuteEbcImageEntryPoint;
- } else {
- Addr = (UINTN) EbcLLEbcInterpret;
+ for (Index = 0; Index < sizeof(mInstructionBufferTemplate) - sizeof(UINTN); Index++) {
+ if (*(UINTN *)&Ptr[Index] == EBC_ENTRYPOINT_SIGNATURE) {
+ *(UINTN *)&Ptr[Index] = (UINTN)EbcEntryPoint;
+ }
+ if (*(UINTN *)&Ptr[Index] == EBC_LL_EBC_ENTRYPOINT_SIGNATURE) {
+ if ((Flags & FLAG_THUNK_ENTRY_POINT) != 0) {
+ *(UINTN *)&Ptr[Index] = (UINTN)EbcLLExecuteEbcImageEntryPoint;
+ } else {
+ *(UINTN *)&Ptr[Index] = (UINTN)EbcLLEbcInterpret;
+ }
+ }
}
//
- // mov r11 Addr => 0x49 0xBB
- //
- *Ptr = 0x49;
- Ptr++;
- Size--;
- *Ptr = 0xBB;
- Ptr++;
- Size--;
- for (Index = 0; Index < sizeof (Addr); Index++) {
- *Ptr = (UINT8) Addr;
- Addr >>= 8;
- Ptr++;
- Size--;
- }
- //
- // Stick in jump opcode bytes for jmp r11 => 0x41 0xFF 0xE3
- //
- *Ptr = 0x41;
- Ptr++;
- Size--;
- *Ptr = 0xFF;
- Ptr++;
- Size--;
- *Ptr = 0xE3;
- Size--;
-
- //
- // Double check that our defined size is ok (application error)
- //
- if (Size < 0) {
- ASSERT (FALSE);
- return EFI_BUFFER_TOO_SMALL;
- }
- //
// Add the thunk to the list for this image. Do this last since the add
// function flushes the cache for us.
//
@@ -533,65 +512,37 @@ EbcLLCALLEX (
{
UINTN IsThunk;
UINTN TargetEbcAddr;
+ UINT8 InstructionBuffer[sizeof(mInstructionBufferTemplate)];
+ UINTN Index;
+ UINTN IndexOfEbcEntrypoint;
IsThunk = 1;
TargetEbcAddr = 0;
+ IndexOfEbcEntrypoint = 0;
//
// Processor specific code to check whether the callee is a thunk to EBC.
//
- if (*((UINT8 *)FuncAddr) != 0x48) {
- IsThunk = 0;
- goto Action;
- }
- if (*((UINT8 *)FuncAddr + 1) != 0xB8) {
- IsThunk = 0;
- goto Action;
- }
- if (*((UINT8 *)FuncAddr + 2) != 0xBC) {
- IsThunk = 0;
- goto Action;
- }
- if (*((UINT8 *)FuncAddr + 3) != 0x2E) {
- IsThunk = 0;
- goto Action;
- }
- if (*((UINT8 *)FuncAddr + 4) != 0x11) {
- IsThunk = 0;
- goto Action;
- }
- if (*((UINT8 *)FuncAddr + 5) != 0xCA) {
- IsThunk = 0;
- goto Action;
- }
- if (*((UINT8 *)FuncAddr + 6) != 0xBC) {
- IsThunk = 0;
- goto Action;
- }
- if (*((UINT8 *)FuncAddr + 7) != 0x2E) {
- IsThunk = 0;
- goto Action;
- }
- if (*((UINT8 *)FuncAddr + 8) != 0x11) {
- IsThunk = 0;
- goto Action;
- }
- if (*((UINT8 *)FuncAddr + 9) != 0xCA) {
- IsThunk = 0;
- goto Action;
- }
- if (*((UINT8 *)FuncAddr + 10) != 0x49) {
- IsThunk = 0;
- goto Action;
+ CopyMem (InstructionBuffer, (VOID *)FuncAddr, sizeof(InstructionBuffer));
+ //
+ // Fill the signature according to mInstructionBufferTemplate
+ //
+ for (Index = 0; Index < sizeof(mInstructionBufferTemplate) - sizeof(UINTN); Index++) {
+ if (*(UINTN *)&mInstructionBufferTemplate[Index] == EBC_ENTRYPOINT_SIGNATURE) {
+ *(UINTN *)&InstructionBuffer[Index] = EBC_ENTRYPOINT_SIGNATURE;
+ IndexOfEbcEntrypoint = Index;
+ }
+ if (*(UINTN *)&mInstructionBufferTemplate[Index] == EBC_LL_EBC_ENTRYPOINT_SIGNATURE) {
+ *(UINTN *)&InstructionBuffer[Index] = EBC_LL_EBC_ENTRYPOINT_SIGNATURE;
+ }
}
- if (*((UINT8 *)FuncAddr + 11) != 0xBA) {
+ //
+ // Check if we need thunk to native
+ //
+ if (CompareMem (InstructionBuffer, mInstructionBufferTemplate, sizeof(mInstructionBufferTemplate)) != 0) {
IsThunk = 0;
- goto Action;
}
- CopyMem (&TargetEbcAddr, (UINT8 *)FuncAddr + 12, 8);
-
-Action:
if (IsThunk == 1){
//
// The callee is a thunk to EBC, adjust the stack pointer down 16 bytes and
@@ -602,8 +553,9 @@ Action:
VmWriteMemN (VmPtr, (UINTN) VmPtr->Gpr[0], (UINTN) FramePtr);
VmPtr->FramePtr = (VOID *) (UINTN) VmPtr->Gpr[0];
VmPtr->Gpr[0] -= 8;
- VmWriteMem64 (VmPtr, (UINTN) VmPtr->Gpr[0], (UINT64) (VmPtr->Ip + Size));
+ VmWriteMem64 (VmPtr, (UINTN) VmPtr->Gpr[0], (UINT64) (UINTN) (VmPtr->Ip + Size));
+ CopyMem (&TargetEbcAddr, (UINT8 *)FuncAddr + IndexOfEbcEntrypoint, sizeof(UINTN));
VmPtr->Ip = (VMIP) (UINTN) TargetEbcAddr;
} else {
//