Leaked source code of windows server 2003
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

906 lines
23 KiB

  1. /*++
  2. Copyright (c) 1989 Microsoft Corporation
  3. Module Name:
  4. dllutil.c
  5. Abstract:
  6. This module contains utility procedures for the Windows Client DLL
  7. Author:
  8. Steve Wood (stevewo) 8-Oct-1990
  9. Revision History:
  10. --*/
  11. #pragma warning(disable:4201) // nameless struct/union
  12. #include "csrdll.h"
  13. NTSTATUS
  14. CsrClientCallServer (
  15. IN OUT PCSR_API_MSG m,
  16. IN OUT PCSR_CAPTURE_HEADER CaptureBuffer OPTIONAL,
  17. IN CSR_API_NUMBER ApiNumber,
  18. IN ULONG ArgLength
  19. )
  20. /*++
  21. Routine Description:
  22. This function sends an API request to the Windows Emulation Subsystem
  23. Server and waits for a reply.
  24. Arguments:
  25. m - Pointer to the API request message to send.
  26. CaptureBuffer - Optional pointer to a capture buffer located in the
  27. Port Memory section that contains additional data being sent
  28. to the server. Since Port Memory is also visible to the server,
  29. no data needs to be copied, but pointers to locations within the
  30. capture buffer need to be converted into pointers valid in the
  31. server's process context, since the server's view of the Port Memory
  32. is not at the same virtual address as the client's view.
  33. ApiNumber - Small integer that is the number of the API being called.
  34. ArgLength - Length, in bytes, of the argument portion located at the
  35. end of the request message. Used to calculate the length of the
  36. request message.
  37. Return Value:
  38. Status Code from either client or server
  39. --*/
  40. {
  41. NTSTATUS Status;
  42. PULONG_PTR PointerOffsets;
  43. ULONG CountPointers;
  44. ULONG_PTR Pointer;
  45. //
  46. // Initialize the header of the message.
  47. //
  48. if ((LONG)ArgLength < 0) {
  49. ArgLength = (ULONG)(-(LONG)ArgLength);
  50. m->h.u2.s2.Type = 0;
  51. }
  52. else {
  53. m->h.u2.ZeroInit = 0;
  54. }
  55. ArgLength |= (ArgLength << 16);
  56. ArgLength += ((sizeof( CSR_API_MSG ) - sizeof( m->u )) << 16) |
  57. (FIELD_OFFSET( CSR_API_MSG, u ) - sizeof( m->h ));
  58. m->h.u1.Length = ArgLength;
  59. m->CaptureBuffer = NULL;
  60. m->ApiNumber = ApiNumber;
  61. //
  62. // if the caller is within the server process, do the API call directly
  63. // and skip the capture buffer fixups and LPC call.
  64. //
  65. if (CsrServerProcess == FALSE) {
  66. //
  67. // If the CaptureBuffer argument is present, then there is data located
  68. // in the Port Memory section that is being passed to the server. All
  69. // Port Memory pointers need to be converted so they are valid in the
  70. // Server's view of the Port Memory.
  71. //
  72. if (ARGUMENT_PRESENT (CaptureBuffer)) {
  73. //
  74. // Store a pointer to the capture buffer in the message that is
  75. // valid in the server process's context.
  76. //
  77. m->CaptureBuffer = (PCSR_CAPTURE_HEADER)
  78. ((PCHAR)CaptureBuffer + CsrPortMemoryRemoteDelta);
  79. //
  80. // Mark the fact that we are done allocating space from the end of
  81. // the capture buffer.
  82. //
  83. CaptureBuffer->FreeSpace = NULL;
  84. //
  85. // Loop over all of the pointers to Port Memory within the message
  86. // itself and convert them into server pointers. Also, convert
  87. // the pointers to pointers into offsets.
  88. //
  89. PointerOffsets = CaptureBuffer->MessagePointerOffsets;
  90. CountPointers = CaptureBuffer->CountMessagePointers;
  91. while (CountPointers--) {
  92. Pointer = *PointerOffsets++;
  93. if (Pointer != 0) {
  94. *(PULONG_PTR)Pointer += CsrPortMemoryRemoteDelta;
  95. PointerOffsets[ -1 ] = Pointer - (ULONG_PTR)m;
  96. }
  97. }
  98. }
  99. //
  100. // Send the request to the server and wait for a reply.
  101. //
  102. Status = NtRequestWaitReplyPort (CsrPortHandle,
  103. (PPORT_MESSAGE)m,
  104. (PPORT_MESSAGE)m);
  105. //
  106. // If the CaptureBuffer argument is present then reverse what we did
  107. // to the pointers above so that the client side code can use them
  108. // again.
  109. //
  110. if (ARGUMENT_PRESENT (CaptureBuffer)) {
  111. //
  112. // Convert the capture buffer pointer back to a client pointer.
  113. //
  114. m->CaptureBuffer = (PCSR_CAPTURE_HEADER)
  115. ((PCHAR)m->CaptureBuffer - CsrPortMemoryRemoteDelta);
  116. //
  117. // Loop over all of the pointers to Port Memory within the message
  118. // itself and convert them into client pointers. Also, convert
  119. // the offset pointers to pointers into back into pointers
  120. //
  121. PointerOffsets = CaptureBuffer->MessagePointerOffsets;
  122. CountPointers = CaptureBuffer->CountMessagePointers;
  123. while (CountPointers--) {
  124. Pointer = *PointerOffsets++;
  125. if (Pointer != 0) {
  126. Pointer += (ULONG_PTR)m;
  127. PointerOffsets[ -1 ] = Pointer;
  128. *(PULONG_PTR)Pointer -= CsrPortMemoryRemoteDelta;
  129. }
  130. }
  131. }
  132. //
  133. // Check for failed status and do something.
  134. //
  135. if (!NT_SUCCESS (Status)) {
  136. #if DBG
  137. if (Status != STATUS_PORT_DISCONNECTED &&
  138. Status != STATUS_INVALID_HANDLE) {
  139. DbgPrint( "CSRDLL: NtRequestWaitReplyPort failed - Status == %X\n",
  140. Status);
  141. }
  142. #endif
  143. m->ReturnValue = Status;
  144. }
  145. } else {
  146. m->h.ClientId = NtCurrentTeb()->ClientId;
  147. Status = (CsrServerApiRoutine) ((PCSR_API_MSG)m,
  148. (PCSR_API_MSG)m);
  149. //
  150. // Check for failed status and do something.
  151. //
  152. if (!NT_SUCCESS( Status )) {
  153. #if DBG
  154. DbgPrint( "CSRDLL: Server side client call failed - Status == %X\n",
  155. Status);
  156. #endif
  157. m->ReturnValue = Status;
  158. }
  159. }
  160. //
  161. // The value of this function is whatever the server function returned.
  162. //
  163. return m->ReturnValue;
  164. }
  165. HANDLE
  166. CsrGetProcessId (
  167. VOID
  168. )
  169. /*++
  170. Routine Description:
  171. This function gets the process ID of the CSR process (for the session)
  172. Arguments:
  173. None
  174. Return Value:
  175. Process ID of CSR
  176. --*/
  177. {
  178. return CsrProcessId;
  179. }
  180. PCSR_CAPTURE_HEADER
  181. CsrAllocateCaptureBuffer (
  182. IN ULONG CountMessagePointers,
  183. IN ULONG Size
  184. )
  185. /*++
  186. Routine Description:
  187. This function allocates a buffer from the Port Memory section for
  188. use by the client in capture arguments into Port Memory. In addition to
  189. specifying the size of the data that needs to be captured, the caller
  190. needs to specify how many pointers to captured data will be passed.
  191. Pointers can be located in either the request message itself, and/or
  192. the capture buffer.
  193. Arguments:
  194. CountMessagePointers - Number of pointers within the request message
  195. that will point to locations within the allocated capture buffer.
  196. Size - Total size of the data that will be captured into the capture
  197. buffer.
  198. Return Value:
  199. A pointer to the capture buffer header.
  200. --*/
  201. {
  202. ULONG CountPointers, SizePointers;
  203. PCSR_CAPTURE_HEADER CaptureBuffer;
  204. ULONG RemainingSize;
  205. //
  206. // Calculate the total number of pointers that will be passed
  207. //
  208. CountPointers = CountMessagePointers;
  209. //
  210. // Calculate the total size of the capture buffer. This includes the
  211. // header, the array of pointer offsets and the data length. We round
  212. // the data length to a 32-bit boundary, assuming that each pointer
  213. // points to data whose length is not aligned on a 32-bit boundary.
  214. //
  215. RemainingSize = (MAXLONG & ~0x3) - FIELD_OFFSET(CSR_CAPTURE_HEADER,
  216. MessagePointerOffsets);
  217. //
  218. // Bail early if too big.
  219. //
  220. if ((Size >= RemainingSize) ||
  221. (CountPointers > (MAXLONG/sizeof(PVOID)))
  222. ) {
  223. return NULL;
  224. }
  225. RemainingSize -= Size;
  226. SizePointers = CountPointers * sizeof(PVOID);
  227. if (SizePointers >= RemainingSize) {
  228. return NULL;
  229. }
  230. RemainingSize -= SizePointers;
  231. if ((CountPointers+1) >= (RemainingSize/3) ) {
  232. return NULL;
  233. }
  234. Size += FIELD_OFFSET(CSR_CAPTURE_HEADER, MessagePointerOffsets) +
  235. SizePointers;
  236. Size = (Size + (3 * (CountPointers+1))) & ~3;
  237. //
  238. // Allocate the capture buffer from the Port Memory Heap.
  239. //
  240. CaptureBuffer = RtlAllocateHeap (CsrPortHeap,
  241. MAKE_CSRPORT_TAG( CAPTURE_TAG ),
  242. Size);
  243. if (CaptureBuffer == NULL) {
  244. //
  245. // FIX, FIX - need to attempt the receive lost reply messages to
  246. // to see if they contain CaptureBuffer pointers that can be freed.
  247. //
  248. return NULL;
  249. }
  250. //
  251. // Initialize the capture buffer header
  252. //
  253. CaptureBuffer->Length = Size;
  254. CaptureBuffer->CountMessagePointers = 0;
  255. //
  256. // If there are pointers being passed then initialize the arrays of
  257. // pointer offsets to zero. In either case set the free space pointer
  258. // in the capture buffer header to point to the first 32-bit aligned
  259. // location after the header, the arrays of pointer offsets are considered
  260. // part of the header.
  261. //
  262. RtlZeroMemory (CaptureBuffer->MessagePointerOffsets,
  263. CountPointers * sizeof (ULONG_PTR));
  264. CaptureBuffer->FreeSpace = (PCHAR)
  265. (CaptureBuffer->MessagePointerOffsets + CountPointers);
  266. //
  267. // Return the address of the capture buffer.
  268. //
  269. return CaptureBuffer;
  270. }
  271. VOID
  272. CsrFreeCaptureBuffer (
  273. IN PCSR_CAPTURE_HEADER CaptureBuffer
  274. )
  275. /*++
  276. Routine Description:
  277. This function frees a capture buffer allocated by CsrAllocateCaptureBuffer.
  278. Arguments:
  279. CaptureBuffer - Pointer to a capture buffer allocated by
  280. CsrAllocateCaptureBuffer.
  281. Return Value:
  282. None.
  283. --*/
  284. {
  285. //
  286. // Free the capture buffer back to the Port Memory heap.
  287. //
  288. RtlFreeHeap (CsrPortHeap, 0, CaptureBuffer);
  289. }
  290. ULONG
  291. CsrAllocateMessagePointer (
  292. IN OUT PCSR_CAPTURE_HEADER CaptureBuffer,
  293. IN ULONG Length,
  294. OUT PVOID *Pointer
  295. )
  296. /*++
  297. Routine Description:
  298. This function allocates space from the capture buffer along with a
  299. pointer to point to it. The pointer is presumed to be located in
  300. the request message structure.
  301. Arguments:
  302. CaptureBuffer - Pointer to a capture buffer allocated by
  303. CsrAllocateCaptureBuffer.
  304. Length - Size of data being allocated from the capture buffer.
  305. Pointer - Address of the pointer within the request message that
  306. is to point to the space allocated out of the capture buffer.
  307. Return Value:
  308. The actual length of the buffer allocated, after it has been rounded
  309. up to a multiple of 4.
  310. --*/
  311. {
  312. if (Length == 0) {
  313. *Pointer = NULL;
  314. Pointer = NULL;
  315. }
  316. else {
  317. //
  318. // Set the returned pointer value to point to the next free byte in
  319. // the capture buffer.
  320. //
  321. *Pointer = CaptureBuffer->FreeSpace;
  322. //
  323. // Round the length up to a multiple of 4
  324. //
  325. if (Length >= MAXLONG) {
  326. //
  327. // Bail early if too big
  328. //
  329. return 0;
  330. }
  331. Length = (Length + 3) & ~3;
  332. //
  333. // Update the free space pointer to point to the next available byte
  334. // in the capture buffer.
  335. //
  336. CaptureBuffer->FreeSpace += Length;
  337. }
  338. //
  339. // Remember the location of this pointer so that CsrClientCallServer can
  340. // convert it into a server pointer prior to sending the request to
  341. // the server.
  342. //
  343. CaptureBuffer->MessagePointerOffsets[ CaptureBuffer->CountMessagePointers++ ] =
  344. (ULONG_PTR)Pointer;
  345. //
  346. // Return the actual length allocated.
  347. //
  348. return Length;
  349. }
  350. VOID
  351. CsrCaptureMessageBuffer (
  352. IN OUT PCSR_CAPTURE_HEADER CaptureBuffer,
  353. IN PVOID Buffer OPTIONAL,
  354. IN ULONG Length,
  355. OUT PVOID *CapturedBuffer
  356. )
  357. /*++
  358. Routine Description:
  359. This function captures a buffer of bytes in an API request message.
  360. Arguments:
  361. CaptureBuffer - Pointer to a capture buffer allocated by
  362. CsrAllocateCaptureBuffer.
  363. Buffer - Optional pointer to the buffer. If this parameter is
  364. not present, then no data is copied into capture buffer.
  365. Length - Length of the buffer.
  366. CapturedBuffer - Pointer to the field in the message that will
  367. be filled in to point to the capture buffer.
  368. Return Value:
  369. None.
  370. --*/
  371. {
  372. //
  373. // Set the length fields of the captured string structure and allocated
  374. // the Length for the string from the capture buffer.
  375. //
  376. CsrAllocateMessagePointer (CaptureBuffer,
  377. Length,
  378. CapturedBuffer);
  379. //
  380. // If Buffer parameter is not present or the length of the data is zero,
  381. // return.
  382. //
  383. if (!ARGUMENT_PRESENT( Buffer ) || (Length == 0)) {
  384. return;
  385. }
  386. //
  387. // Copy the buffer data to the capture area.
  388. //
  389. RtlMoveMemory (*CapturedBuffer, Buffer, Length);
  390. return;
  391. }
  392. VOID
  393. CsrCaptureMessageString (
  394. IN OUT PCSR_CAPTURE_HEADER CaptureBuffer,
  395. IN PCSTR String OPTIONAL,
  396. IN ULONG Length,
  397. IN ULONG MaximumLength,
  398. OUT PSTRING CapturedString
  399. )
  400. /*++
  401. Routine Description:
  402. This function captures an ASCII string into a counted string data
  403. structure located in an API request message.
  404. Arguments:
  405. CaptureBuffer - Pointer to a capture buffer allocated by
  406. CsrAllocateCaptureBuffer.
  407. String - Optional pointer to the ASCII string. If this parameter is
  408. not present, then the counted string data structure is set to
  409. the null string.
  410. Length - Length of the ASCII string, ignored if String is NULL.
  411. MaximumLength - Maximum length of the string. Different for null
  412. terminated strings, where Length does not include the null and
  413. MaximumLength does. This is always how much space is allocated
  414. from the capture buffer.
  415. CaptureString - Pointer to the counted string data structure that will
  416. be filled in to point to the captured ASCII string.
  417. Return Value:
  418. None.
  419. --*/
  420. {
  421. ASSERT(CapturedString != NULL);
  422. //
  423. // If String parameter is not present, then set the captured string
  424. // to be the null string and return.
  425. //
  426. if (!ARGUMENT_PRESENT( String )) {
  427. CapturedString->Length = 0;
  428. CapturedString->MaximumLength = (USHORT)MaximumLength;
  429. CsrAllocateMessagePointer( CaptureBuffer,
  430. MaximumLength,
  431. (PVOID *)&CapturedString->Buffer
  432. );
  433. //
  434. // Make it NULL terminated if there is any room.
  435. //
  436. if (MaximumLength != 0) {
  437. CapturedString->Buffer[0] = 0;
  438. }
  439. return;
  440. }
  441. //
  442. // Set the length fields of the captured string structure and allocated
  443. // the MaximumLength for the string from the capture buffer.
  444. //
  445. CapturedString->Length = (USHORT)Length;
  446. CapturedString->MaximumLength = (USHORT)
  447. CsrAllocateMessagePointer( CaptureBuffer,
  448. MaximumLength,
  449. (PVOID *)&CapturedString->Buffer
  450. );
  451. //
  452. // If the Length of the ASCII string is non-zero then move it to the
  453. // capture area.
  454. //
  455. if (Length != 0) {
  456. RtlMoveMemory (CapturedString->Buffer, String, MaximumLength );
  457. }
  458. if (CapturedString->Length < CapturedString->MaximumLength) {
  459. CapturedString->Buffer[ CapturedString->Length ] = '\0';
  460. }
  461. return;
  462. }
  463. VOID
  464. CsrCaptureMessageUnicodeStringInPlace (
  465. IN OUT PCSR_CAPTURE_HEADER CaptureBuffer,
  466. IN OUT PUNICODE_STRING String
  467. )
  468. /*++
  469. Routine Description:
  470. This function captures an ASCII string into a counted string data
  471. structure located in an API request message.
  472. Arguments:
  473. CaptureBuffer - Pointer to a capture buffer allocated by
  474. CsrAllocateCaptureBuffer.
  475. String - Optional pointer to the Unicode string. If this parameter is
  476. not present, then the counted string data structure is set to
  477. the null string.
  478. Length - Length of the Unicode string in bytes, ignored if String is NULL.
  479. MaximumLength - Maximum length of the string. Different for null
  480. terminated strings, where Length does not include the null and
  481. MaximumLength does. This is always how much space is allocated
  482. from the capture buffer.
  483. CaptureString - Pointer to the counted string data structure that will
  484. be filled in to point to the captured Unicode string.
  485. Return Value:
  486. None, but if you don't trust the String parameter, use a __try block.
  487. --*/
  488. {
  489. ASSERT(String != NULL);
  490. CsrCaptureMessageString (CaptureBuffer,
  491. (PCSTR)String->Buffer,
  492. String->Length,
  493. String->MaximumLength,
  494. (PSTRING)String);
  495. // test > before substraction due to unsignedness
  496. if (String->MaximumLength > String->Length) {
  497. if ((String->MaximumLength - String->Length) >= sizeof(WCHAR)) {
  498. String->Buffer[ String->Length / sizeof(WCHAR) ] = 0;
  499. }
  500. }
  501. }
  502. NTSTATUS
  503. CsrCaptureMessageMultiUnicodeStringsInPlace (
  504. IN OUT PCSR_CAPTURE_HEADER* InOutCaptureBuffer,
  505. IN ULONG NumberOfStringsToCapture,
  506. IN const PUNICODE_STRING* StringsToCapture
  507. )
  508. /*++
  509. Routine Description:
  510. Capture multiple unicode strings.
  511. If the CaptureBuffer hasn't been allocated yet (passed as NULL), first
  512. allocate it.
  513. Arguments:
  514. CaptureBuffer - Pointer to a capture buffer allocated by
  515. CsrAllocateCaptureBuffer, or NULL, in which case we call CsrAllocateCaptureBuffer
  516. for you; this is the case if you are only capturing these strings
  517. and nothing else.
  518. NumberOfStringsToCapture -
  519. StringsToCapture -
  520. Return Value:
  521. NTSTATUS
  522. --*/
  523. {
  524. ULONG Length = 0;
  525. ULONG i = 0;
  526. PCSR_CAPTURE_HEADER CaptureBuffer = NULL;
  527. if (InOutCaptureBuffer == NULL) {
  528. return STATUS_INVALID_PARAMETER;
  529. }
  530. CaptureBuffer = *InOutCaptureBuffer;
  531. if (CaptureBuffer == NULL) {
  532. Length = 0;
  533. for (i = 0 ; i != NumberOfStringsToCapture ; ++i) {
  534. if (StringsToCapture[i] != NULL) {
  535. Length += StringsToCapture[i]->MaximumLength;
  536. }
  537. }
  538. CaptureBuffer = CsrAllocateCaptureBuffer(NumberOfStringsToCapture, Length);
  539. if (CaptureBuffer == NULL) {
  540. return STATUS_NO_MEMORY;
  541. }
  542. *InOutCaptureBuffer = CaptureBuffer;
  543. }
  544. for (i = 0 ; i != NumberOfStringsToCapture ; i += 1) {
  545. if (StringsToCapture[i] != NULL) {
  546. CsrCaptureMessageUnicodeStringInPlace (CaptureBuffer,
  547. StringsToCapture[i]);
  548. }
  549. }
  550. return STATUS_SUCCESS;
  551. }
  552. PLARGE_INTEGER
  553. CsrCaptureTimeout (
  554. IN ULONG MilliSeconds,
  555. OUT PLARGE_INTEGER Timeout
  556. )
  557. {
  558. if (MilliSeconds == -1) {
  559. return NULL;
  560. }
  561. Timeout->QuadPart = Int32x32To64( MilliSeconds, -10000 );
  562. return (PLARGE_INTEGER)Timeout;
  563. }
  564. VOID
  565. CsrProbeForWrite (
  566. IN PVOID Address,
  567. IN ULONG Length,
  568. IN ULONG Alignment
  569. )
  570. /*++
  571. Routine Description:
  572. This function probes a structure for read accessibility.
  573. If the structure is not accessible, then an exception is raised.
  574. Arguments:
  575. Address - Supplies a pointer to the structure to be probed.
  576. Length - Supplies the length of the structure.
  577. Alignment - Supplies the required alignment of the structure expressed
  578. as the number of bytes in the primitive datatype (e.g., 1 for char,
  579. 2 for short, 4 for long, and 8 for quad).
  580. Return Value:
  581. None.
  582. --*/
  583. {
  584. CHAR Temp;
  585. volatile CHAR *StartAddress;
  586. volatile CHAR *EndAddress;
  587. //
  588. // If the structure has zero length, then do not probe the structure for
  589. // write accessibility or alignment.
  590. //
  591. if (Length != 0) {
  592. //
  593. // If the structure is not properly aligned, then raise a data
  594. // misalignment exception.
  595. //
  596. ASSERT((Alignment == 1) || (Alignment == 2) ||
  597. (Alignment == 4) || (Alignment == 8));
  598. StartAddress = (volatile CHAR *)Address;
  599. if (((ULONG_PTR)StartAddress & (Alignment - 1)) != 0) {
  600. RtlRaiseStatus(STATUS_DATATYPE_MISALIGNMENT);
  601. } else {
  602. Temp = *StartAddress;
  603. *StartAddress = Temp;
  604. EndAddress = StartAddress + Length - 1;
  605. Temp = *EndAddress;
  606. *EndAddress = Temp;
  607. }
  608. }
  609. }
  610. VOID
  611. CsrProbeForRead (
  612. IN PVOID Address,
  613. IN ULONG Length,
  614. IN ULONG Alignment
  615. )
  616. /*++
  617. Routine Description:
  618. This function probes a structure for read accessibility.
  619. If the structure is not accessible, then an exception is raised.
  620. Arguments:
  621. Address - Supplies a pointer to the structure to be probed.
  622. Length - Supplies the length of the structure.
  623. Alignment - Supplies the required alignment of the structure expressed
  624. as the number of bytes in the primitive datatype (e.g., 1 for char,
  625. 2 for short, 4 for long, and 8 for quad).
  626. Return Value:
  627. None.
  628. --*/
  629. {
  630. CHAR Temp;
  631. volatile CHAR *StartAddress;
  632. volatile CHAR *EndAddress;
  633. //
  634. // If the structure has zero length, then do not probe the structure for
  635. // read accessibility or alignment.
  636. //
  637. if (Length != 0) {
  638. //
  639. // If the structure is not properly aligned, then raise a data
  640. // misalignment exception.
  641. //
  642. ASSERT((Alignment == 1) || (Alignment == 2) ||
  643. (Alignment == 4) || (Alignment == 8));
  644. StartAddress = (volatile CHAR *)Address;
  645. if (((ULONG_PTR)StartAddress & (Alignment - 1)) != 0) {
  646. RtlRaiseStatus(STATUS_DATATYPE_MISALIGNMENT);
  647. } else {
  648. Temp = *StartAddress;
  649. EndAddress = StartAddress + Length - 1;
  650. Temp = *EndAddress;
  651. }
  652. }
  653. }