Changeset 84980 in vbox
- Timestamp:
- Jun 29, 2020 8:44:28 AM (5 years ago)
- File:
-
- 1 edited
Legend:
- Unmodified
- Added
- Removed
-
trunk/src/VBox/Debugger/DBGPlugInLinux.cpp
r82968 r84980 182 182 /** The max kernel size. */ 183 183 #define LNX_MAX_KERNEL_SIZE UINT32_C(0x0f000000) 184 /** Maximum kernel log buffer size. */ 185 #define LNX_MAX_KERNEL_LOG_SIZE (16 * _1M) 184 186 185 187 /** The maximum size we expect for kallsyms_names. */ … … 223 225 224 226 static const uint8_t g_abLinuxVersion[] = "Linux version "; 227 /** The needle for searching for the kernel log area (the value is observed in pretty much all 32bit and 64bit x86 kernels). 228 * This needle should appear only once in the memory due to the address being filled in by a format string. */ 229 static const uint8_t g_abKrnlLogNeedle[] = "BIOS-e820: [mem 0x0000000000000000"; 230 231 232 /** 233 * Tries to resolve the kernel log buffer start and end by searching for needle. 234 * 235 * @returns VBox status code. 236 * @param pThis The Linux digger data. 237 * @param pUVM The VM handle. 238 * @param pGCPtrLogBuf Where to store the start of the kernel log buffer on success. 239 * @param pcbLogBuf Where to store the size of the kernel log buffer on success. 240 */ 241 static int dbgDiggerLinuxKrnlLogBufFindByNeedle(PDBGDIGGERLINUX pThis, PUVM pUVM, RTGCPTR *pGCPtrLogBuf, uint32_t *pcbLogBuf) 242 { 243 int rc = VINF_SUCCESS; 244 245 /* Try to find the needle, it should be very early in the kernel log buffer. */ 246 DBGFADDRESS AddrScan; 247 DBGFADDRESS AddrHit; 248 DBGFR3AddrFromFlat(pUVM, &AddrScan, pThis->f64Bit ? LNX64_KERNEL_ADDRESS_START : LNX32_KERNEL_ADDRESS_START); 249 250 rc = DBGFR3MemScan(pUVM, 0 /*idCpu*/, &AddrScan, ~(RTGCUINTPTR)0, 1 /*uAlign*/, 251 g_abKrnlLogNeedle, sizeof(g_abKrnlLogNeedle) - 1, &AddrHit); 252 if (RT_SUCCESS(rc)) 253 { 254 size_t cbLogBuf = 0; 255 uint64_t tsLastNs = 0; 256 DBGFADDRESS AddrCur; 257 258 DBGFR3AddrSub(&AddrHit, sizeof(LNXPRINTKHDR)); 259 AddrCur = AddrHit; 260 261 /* Try to find the end of the kernel log buffer. */ 262 for (;;) 263 { 264 if (cbLogBuf >= LNX_MAX_KERNEL_LOG_SIZE) 265 break; 266 267 LNXPRINTKHDR Hdr; 268 rc = DBGFR3MemRead(pUVM, 0 /*idCpu*/, &AddrCur, &Hdr, sizeof(Hdr)); 269 if (RT_SUCCESS(rc)) 270 { 271 uint32_t const cbLogAlign = 4; 272 273 /* 274 * If the header does not look valid anymore we stop. 275 * Timestamps are monotonically increasing. 276 */ 277 if ( !Hdr.cbTotal /* Zero entry size means there is no record anymore, doesn't make sense to look futher. */ 278 || Hdr.cbText + Hdr.cbDict + sizeof(Hdr) > Hdr.cbTotal 279 || (Hdr.cbTotal & (cbLogAlign - 1)) != 0 280 || tsLastNs > Hdr.nsTimestamp) 281 break; 282 283 /** @todo Maybe read text part and verify it is all ASCII. */ 284 285 cbLogBuf += Hdr.cbTotal; 286 DBGFR3AddrAdd(&AddrCur, Hdr.cbTotal); 287 } 288 289 if (RT_FAILURE(rc)) 290 break; 291 } 292 293 /** @todo Go back to find the start address of the kernel log (or we loose potential kernel log messages). */ 294 295 if (RT_SUCCESS(rc)) 296 { 297 /* Align log buffer size to a power of two. */ 298 if (cbLogBuf && !RT_IS_POWER_OF_TWO(cbLogBuf)) 299 { 300 uint32_t i = 0; 301 302 while (cbLogBuf) 303 { 304 cbLogBuf >>= 1; 305 i++; 306 } 307 308 cbLogBuf = 1 << i; 309 } 310 311 *pGCPtrLogBuf = AddrHit.FlatPtr; 312 *pcbLogBuf = RT_MIN(cbLogBuf, LNX_MAX_KERNEL_LOG_SIZE); 313 } 314 } 315 316 return rc; 317 } 318 225 319 226 320 /** … … 758 852 } 759 853 854 855 /** 856 * Worker to process a given record based kernel log. 857 * 858 * @returns VBox status code. 859 * @param pThis The Linux digger data. 860 * @param pUVM The VM user mode handle. 861 * @param GCPtrLogBuf Flat guest address of the start of the log buffer. 862 * @param cbLogBuf Power of two aligned size of the log buffer. 863 * @param idxFirst Index in the log bfufer of the first message. 864 * @param idxNext Index where to write hte next message in the log buffer. 865 * @param fFlags Flags reserved for future use, MBZ. 866 * @param cMessages The number of messages to retrieve, counting from the 867 * end of the log (i.e. like tail), use UINT32_MAX for all. 868 * @param pszBuf The output buffer. 869 * @param cbBuf The buffer size. 870 * @param pcbActual Where to store the number of bytes actually returned, 871 * including zero terminator. On VERR_BUFFER_OVERFLOW this 872 * holds the necessary buffer size. Optional. 873 */ 874 static int dbgDiggerLinuxKrnLogBufferProcess(PDBGDIGGERLINUX pThis, PUVM pUVM, RTGCPTR GCPtrLogBuf, 875 uint32_t cbLogBuf, uint32_t idxFirst, uint32_t idxNext, 876 uint32_t fFlags, uint32_t cMessages, char *pszBuf, size_t cbBuf, 877 size_t *pcbActual) 878 { 879 RT_NOREF(fFlags); 880 881 /* 882 * Check if the values make sense. 883 */ 884 if (pThis->f64Bit ? !LNX64_VALID_ADDRESS(GCPtrLogBuf) : !LNX32_VALID_ADDRESS(GCPtrLogBuf)) 885 { 886 LogRel(("dbgDiggerLinuxIDmsg_QueryKernelLog: 'log_buf' value %RGv is not valid.\n", GCPtrLogBuf)); 887 return VERR_NOT_FOUND; 888 } 889 if ( cbLogBuf < _4K 890 || !RT_IS_POWER_OF_TWO(cbLogBuf) 891 || cbLogBuf > LNX_MAX_KERNEL_LOG_SIZE) 892 { 893 LogRel(("dbgDiggerLinuxIDmsg_QueryKernelLog: 'log_buf_len' value %#x is not valid.\n", cbLogBuf)); 894 return VERR_NOT_FOUND; 895 } 896 uint32_t const cbLogAlign = 4; 897 if ( idxFirst > cbLogBuf - sizeof(LNXPRINTKHDR) 898 || (idxFirst & (cbLogAlign - 1)) != 0) 899 { 900 LogRel(("dbgDiggerLinuxIDmsg_QueryKernelLog: 'log_first_idx' value %#x is not valid.\n", idxFirst)); 901 return VERR_NOT_FOUND; 902 } 903 if ( idxNext > cbLogBuf - sizeof(LNXPRINTKHDR) 904 || (idxNext & (cbLogAlign - 1)) != 0) 905 { 906 LogRel(("dbgDiggerLinuxIDmsg_QueryKernelLog: 'log_next_idx' value %#x is not valid.\n", idxNext)); 907 return VERR_NOT_FOUND; 908 } 909 910 /* 911 * Read the whole log buffer. 912 */ 913 uint8_t *pbLogBuf = (uint8_t *)RTMemAlloc(cbLogBuf); 914 if (!pbLogBuf) 915 { 916 LogRel(("dbgDiggerLinuxIDmsg_QueryKernelLog: Failed to allocate %#x bytes for log buffer\n", cbLogBuf)); 917 return VERR_NO_MEMORY; 918 } 919 DBGFADDRESS Addr; 920 int rc = DBGFR3MemRead(pUVM, 0 /*idCpu*/, DBGFR3AddrFromFlat(pUVM, &Addr, GCPtrLogBuf), pbLogBuf, cbLogBuf); 921 if (RT_FAILURE(rc)) 922 { 923 LogRel(("dbgDiggerLinuxIDmsg_QueryKernelLog: Error reading %#x bytes of log buffer at %RGv: %Rrc\n", 924 cbLogBuf, Addr.FlatPtr, rc)); 925 RTMemFree(pbLogBuf); 926 return VERR_NOT_FOUND; 927 } 928 929 /* 930 * Count the messages in the buffer while doing some basic validation. 931 */ 932 uint32_t const cbUsed = idxFirst == idxNext ? cbLogBuf /* could be empty... */ 933 : idxFirst < idxNext ? idxNext - idxFirst : cbLogBuf - idxFirst + idxNext; 934 uint32_t cbLeft = cbUsed; 935 uint32_t offCur = idxFirst; 936 uint32_t cLogMsgs = 0; 937 938 while (cbLeft > 0) 939 { 940 PCLNXPRINTKHDR pHdr = (PCLNXPRINTKHDR)&pbLogBuf[offCur]; 941 if (!pHdr->cbTotal) 942 { 943 /* Wrap around packet, most likely... */ 944 if (cbLogBuf - offCur >= cbLeft) 945 break; 946 offCur = 0; 947 pHdr = (PCLNXPRINTKHDR)&pbLogBuf[offCur]; 948 } 949 if (RT_UNLIKELY( pHdr->cbTotal > cbLogBuf - sizeof(*pHdr) - offCur 950 || pHdr->cbTotal > cbLeft 951 || (pHdr->cbTotal & (cbLogAlign - 1)) != 0 952 || pHdr->cbTotal < (uint32_t)pHdr->cbText + (uint32_t)pHdr->cbDict + sizeof(*pHdr) )) 953 { 954 LogRel(("dbgDiggerLinuxIDmsg_QueryKernelLog: Invalid printk_log record at %#x: cbTotal=%#x cbText=%#x cbDict=%#x cbLogBuf=%#x cbLeft=%#x\n", 955 offCur, pHdr->cbTotal, pHdr->cbText, pHdr->cbDict, cbLogBuf, cbLeft)); 956 break; 957 } 958 959 if (pHdr->cbText > 0) 960 cLogMsgs++; 961 962 /* next */ 963 offCur += pHdr->cbTotal; 964 cbLeft -= pHdr->cbTotal; 965 } 966 if (!cLogMsgs) 967 { 968 RTMemFree(pbLogBuf); 969 return VERR_NOT_FOUND; 970 } 971 972 /* 973 * Copy the messages into the output buffer. 974 */ 975 offCur = idxFirst; 976 cbLeft = cbUsed - cbLeft; 977 978 /* Skip messages that the caller doesn't want. */ 979 if (cMessages < cLogMsgs) 980 { 981 uint32_t cToSkip = cLogMsgs - cMessages; 982 cLogMsgs - cToSkip; 983 984 while (cToSkip > 0) 985 { 986 PCLNXPRINTKHDR pHdr = (PCLNXPRINTKHDR)&pbLogBuf[offCur]; 987 if (!pHdr->cbTotal) 988 { 989 offCur = 0; 990 pHdr = (PCLNXPRINTKHDR)&pbLogBuf[offCur]; 991 } 992 if (pHdr->cbText > 0) 993 cToSkip--; 994 995 /* next */ 996 offCur += pHdr->cbTotal; 997 cbLeft -= pHdr->cbTotal; 998 } 999 } 1000 1001 /* Now copy the messages. */ 1002 size_t offDst = 0; 1003 while (cbLeft > 0) 1004 { 1005 PCLNXPRINTKHDR pHdr = (PCLNXPRINTKHDR)&pbLogBuf[offCur]; 1006 if ( !pHdr->cbTotal 1007 || !cLogMsgs) 1008 { 1009 if (cbLogBuf - offCur >= cbLeft) 1010 break; 1011 offCur = 0; 1012 pHdr = (PCLNXPRINTKHDR)&pbLogBuf[offCur]; 1013 } 1014 1015 if (pHdr->cbText > 0) 1016 { 1017 char *pchText = (char *)(pHdr + 1); 1018 size_t cchText = RTStrNLen(pchText, pHdr->cbText); 1019 if (offDst + cchText < cbBuf) 1020 { 1021 memcpy(&pszBuf[offDst], pHdr + 1, cchText); 1022 pszBuf[offDst + cchText] = '\n'; 1023 } 1024 else if (offDst < cbBuf) 1025 memcpy(&pszBuf[offDst], pHdr + 1, cbBuf - offDst); 1026 offDst += cchText + 1; 1027 } 1028 1029 /* next */ 1030 offCur += pHdr->cbTotal; 1031 cbLeft -= pHdr->cbTotal; 1032 } 1033 1034 /* Done with the buffer. */ 1035 RTMemFree(pbLogBuf); 1036 1037 /* Make sure we've reserved a char for the terminator. */ 1038 if (!offDst) 1039 offDst = 1; 1040 1041 /* Set return size value. */ 1042 if (pcbActual) 1043 *pcbActual = offDst; 1044 1045 if (offDst <= cbBuf) 1046 return VINF_SUCCESS; 1047 return VERR_BUFFER_OVERFLOW; 1048 } 1049 1050 760 1051 /** 761 1052 * Worker to get at the kernel log for post 3.4 kernels where the log buffer contains records. … … 778 1069 char *pszBuf, size_t cbBuf, size_t *pcbActual) 779 1070 { 780 RT_NOREF1(fFlags);781 1071 int rc = VINF_SUCCESS; 782 1072 RTGCPTR GCPtrLogBuf; … … 826 1116 rc = dbgDiggerLinuxQueryLogBufferPtrs(pThis, pUVM, hMod, &GCPtrLogBuf, &cbLogBuf); 827 1117 if (RT_FAILURE(rc)) 1118 { 1119 /* 1120 * Last resort, scan for a known value which should appear only once in the kernel log buffer 1121 * and try to deduce the boundaries from there. 1122 */ 1123 rc = dbgDiggerLinuxKrnlLogBufFindByNeedle(pThis, pUVM, &GCPtrLogBuf, &cbLogBuf); 828 1124 return rc; 829 } 830 831 /* 832 * Check if the values make sense. 833 */ 834 if (pThis->f64Bit ? !LNX64_VALID_ADDRESS(GCPtrLogBuf) : !LNX32_VALID_ADDRESS(GCPtrLogBuf)) 835 { 836 LogRel(("dbgDiggerLinuxIDmsg_QueryKernelLog: 'log_buf' value %RGv is not valid.\n", GCPtrLogBuf)); 837 return VERR_NOT_FOUND; 838 } 839 if ( cbLogBuf < 4096 840 || !RT_IS_POWER_OF_TWO(cbLogBuf) 841 || cbLogBuf > 16*_1M) 842 { 843 LogRel(("dbgDiggerLinuxIDmsg_QueryKernelLog: 'log_buf_len' value %#x is not valid.\n", cbLogBuf)); 844 return VERR_NOT_FOUND; 845 } 846 uint32_t const cbLogAlign = 4; 847 if ( idxFirst > cbLogBuf - sizeof(LNXPRINTKHDR) 848 || (idxFirst & (cbLogAlign - 1)) != 0) 849 { 850 LogRel(("dbgDiggerLinuxIDmsg_QueryKernelLog: 'log_first_idx' value %#x is not valid.\n", idxFirst)); 851 return VERR_NOT_FOUND; 852 } 853 if ( idxNext > cbLogBuf - sizeof(LNXPRINTKHDR) 854 || (idxNext & (cbLogAlign - 1)) != 0) 855 { 856 LogRel(("dbgDiggerLinuxIDmsg_QueryKernelLog: 'log_next_idx' value %#x is not valid.\n", idxNext)); 857 return VERR_NOT_FOUND; 858 } 859 860 /* 861 * Read the whole log buffer. 862 */ 863 uint8_t *pbLogBuf = (uint8_t *)RTMemAlloc(cbLogBuf); 864 if (!pbLogBuf) 865 { 866 LogRel(("dbgDiggerLinuxIDmsg_QueryKernelLog: Failed to allocate %#x bytes for log buffer\n", cbLogBuf)); 867 return VERR_NO_MEMORY; 868 } 869 DBGFADDRESS Addr; 870 rc = DBGFR3MemRead(pUVM, 0 /*idCpu*/, DBGFR3AddrFromFlat(pUVM, &Addr, GCPtrLogBuf), pbLogBuf, cbLogBuf); 871 if (RT_FAILURE(rc)) 872 { 873 LogRel(("dbgDiggerLinuxIDmsg_QueryKernelLog: Error reading %#x bytes of log buffer at %RGv: %Rrc\n", 874 cbLogBuf, Addr.FlatPtr, rc)); 875 RTMemFree(pbLogBuf); 876 return VERR_NOT_FOUND; 877 } 878 879 /* 880 * Count the messages in the buffer while doing some basic validation. 881 */ 882 uint32_t const cbUsed = idxFirst == idxNext ? cbLogBuf /* could be empty... */ 883 : idxFirst < idxNext ? idxNext - idxFirst : cbLogBuf - idxFirst + idxNext; 884 uint32_t cbLeft = cbUsed; 885 uint32_t offCur = idxFirst; 886 uint32_t cLogMsgs = 0; 887 888 while (cbLeft > 0) 889 { 890 PCLNXPRINTKHDR pHdr = (PCLNXPRINTKHDR)&pbLogBuf[offCur]; 891 if (!pHdr->cbTotal) 892 { 893 /* Wrap around packet, most likely... */ 894 if (cbLogBuf - offCur >= cbLeft) 895 break; 896 offCur = 0; 897 pHdr = (PCLNXPRINTKHDR)&pbLogBuf[offCur]; 898 } 899 if (RT_UNLIKELY( pHdr->cbTotal > cbLogBuf - sizeof(*pHdr) - offCur 900 || pHdr->cbTotal > cbLeft 901 || (pHdr->cbTotal & (cbLogAlign - 1)) != 0 902 || pHdr->cbTotal < (uint32_t)pHdr->cbText + (uint32_t)pHdr->cbDict + sizeof(*pHdr) )) 903 { 904 LogRel(("dbgDiggerLinuxIDmsg_QueryKernelLog: Invalid printk_log record at %#x: cbTotal=%#x cbText=%#x cbDict=%#x cbLogBuf=%#x cbLeft=%#x\n", 905 offCur, pHdr->cbTotal, pHdr->cbText, pHdr->cbDict, cbLogBuf, cbLeft)); 906 rc = VERR_INVALID_STATE; 907 break; 908 } 909 910 if (pHdr->cbText > 0) 911 cLogMsgs++; 912 913 /* next */ 914 offCur += pHdr->cbTotal; 915 cbLeft -= pHdr->cbTotal; 916 } 917 if (RT_FAILURE(rc)) 918 { 919 RTMemFree(pbLogBuf); 920 return rc; 921 } 922 923 /* 924 * Copy the messages into the output buffer. 925 */ 926 offCur = idxFirst; 927 cbLeft = cbUsed; 928 929 /* Skip messages that the caller doesn't want. */ 930 if (cMessages < cLogMsgs) 931 { 932 uint32_t cToSkip = cLogMsgs - cMessages; 933 while (cToSkip > 0) 934 { 935 PCLNXPRINTKHDR pHdr = (PCLNXPRINTKHDR)&pbLogBuf[offCur]; 936 if (!pHdr->cbTotal) 937 { 938 offCur = 0; 939 pHdr = (PCLNXPRINTKHDR)&pbLogBuf[offCur]; 940 } 941 if (pHdr->cbText > 0) 942 cToSkip--; 943 944 /* next */ 945 offCur += pHdr->cbTotal; 946 cbLeft -= pHdr->cbTotal; 947 } 948 } 949 950 /* Now copy the messages. */ 951 size_t offDst = 0; 952 while (cbLeft > 0) 953 { 954 PCLNXPRINTKHDR pHdr = (PCLNXPRINTKHDR)&pbLogBuf[offCur]; 955 if (!pHdr->cbTotal) 956 { 957 if (cbLogBuf - offCur >= cbLeft) 958 break; 959 offCur = 0; 960 pHdr = (PCLNXPRINTKHDR)&pbLogBuf[offCur]; 961 } 962 963 if (pHdr->cbText > 0) 964 { 965 char *pchText = (char *)(pHdr + 1); 966 size_t cchText = RTStrNLen(pchText, pHdr->cbText); 967 if (offDst + cchText < cbBuf) 968 { 969 memcpy(&pszBuf[offDst], pHdr + 1, cchText); 970 pszBuf[offDst + cchText] = '\n'; 971 } 972 else if (offDst < cbBuf) 973 memcpy(&pszBuf[offDst], pHdr + 1, cbBuf - offDst); 974 offDst += cchText + 1; 975 } 976 977 /* next */ 978 offCur += pHdr->cbTotal; 979 cbLeft -= pHdr->cbTotal; 980 } 981 982 /* Done with the buffer. */ 983 RTMemFree(pbLogBuf); 984 985 /* Make sure we've reserved a char for the terminator. */ 986 if (!offDst) 987 offDst = 1; 988 989 /* Set return size value. */ 990 if (pcbActual) 991 *pcbActual = offDst; 992 993 if (offDst <= cbBuf) 994 return VINF_SUCCESS; 995 return VERR_BUFFER_OVERFLOW; 1125 } 1126 } 1127 1128 return dbgDiggerLinuxKrnLogBufferProcess(pThis, pUVM, GCPtrLogBuf, cbLogBuf, idxFirst, idxNext, 1129 fFlags, cMessages, pszBuf, cbBuf, pcbActual); 996 1130 } 997 1131 … … 1014 1148 int rc = RTDbgAsModuleByName(hAs, "vmlinux", 0, &hMod); 1015 1149 RTDbgAsRelease(hAs); 1016 if (RT_FAILURE(rc)) 1017 return VERR_NOT_FOUND; 1018 1019 /* 1020 * Check whether the kernel log buffer is a simple char buffer or the newer 1021 * record based implementation. 1022 * The record based implementation was presumably introduced with kernel 3.4, 1023 * see: http://thread.gmane.org/gmane.linux.kernel/1284184 1024 */ 1025 size_t cbActual; 1026 if (dbgDiggerLinuxLogBufferIsAsciiBuffer(pData, pUVM)) 1027 rc = dbgDiggerLinuxLogBufferQueryAscii(pData, pUVM, hMod, fFlags, cMessages, pszBuf, cbBuf, &cbActual); 1150 1151 size_t cbActual = 0; 1152 if (RT_SUCCESS(rc)) 1153 { 1154 /* 1155 * Check whether the kernel log buffer is a simple char buffer or the newer 1156 * record based implementation. 1157 * The record based implementation was presumably introduced with kernel 3.4, 1158 * see: http://thread.gmane.org/gmane.linux.kernel/1284184 1159 */ 1160 if (dbgDiggerLinuxLogBufferIsAsciiBuffer(pData, pUVM)) 1161 rc = dbgDiggerLinuxLogBufferQueryAscii(pData, pUVM, hMod, fFlags, cMessages, pszBuf, cbBuf, &cbActual); 1162 else 1163 rc = dbgDiggerLinuxLogBufferQueryRecords(pData, pUVM, hMod, fFlags, cMessages, pszBuf, cbBuf, &cbActual); 1164 1165 /* Release the module in any case. */ 1166 RTDbgModRelease(hMod); 1167 } 1028 1168 else 1029 rc = dbgDiggerLinuxLogBufferQueryRecords(pData, pUVM, hMod, fFlags, cMessages, pszBuf, cbBuf, &cbActual); 1030 1031 /* Release the module in any case. */ 1032 RTDbgModRelease(hMod); 1169 { 1170 /* 1171 * For the record based kernel versions we have a last resort heuristic which doesn't 1172 * require any symbols, try that here. 1173 */ 1174 if (!dbgDiggerLinuxLogBufferIsAsciiBuffer(pData, pUVM)) 1175 { 1176 RTGCPTR GCPtrLogBuf = 0; 1177 uint32_t cbLogBuf = 0; 1178 1179 rc = dbgDiggerLinuxKrnlLogBufFindByNeedle(pData, pUVM, &GCPtrLogBuf, &cbLogBuf); 1180 if (RT_SUCCESS(rc)) 1181 rc = dbgDiggerLinuxKrnLogBufferProcess(pData, pUVM, GCPtrLogBuf, cbLogBuf, 0 /*idxFirst*/, 0 /*idxNext*/, 1182 fFlags, cMessages, pszBuf, cbBuf, &cbActual); 1183 } 1184 else 1185 rc = VERR_NOT_FOUND; 1186 } 1033 1187 1034 1188 if (RT_FAILURE(rc) && rc != VERR_BUFFER_OVERFLOW)
Note:
See TracChangeset
for help on using the changeset viewer.