|
1 /* |
|
2 * Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). |
|
3 * All rights reserved. |
|
4 * This component and the accompanying materials are made available |
|
5 * under the terms of "Eclipse Public License v1.0" |
|
6 * which accompanies this distribution, and is available |
|
7 * at the URL "http://www.eclipse.org/legal/epl-v10.html". |
|
8 * |
|
9 * Initial Contributors: |
|
10 * Nokia Corporation - initial contribution. |
|
11 * |
|
12 * Contributors: |
|
13 * |
|
14 * Description: |
|
15 * |
|
16 */ |
|
17 |
|
18 #include "MemSpyDriverLogChanThreadAndProcess.h" |
|
19 |
|
20 // System includes |
|
21 #include <platform.h> |
|
22 #include <memspy/driver/memspydriverobjectsshared.h> |
|
23 #include <memspy/driver/memspydriverpanics.h> |
|
24 |
|
25 // Shared includes |
|
26 #include "MemSpyDriverOpCodes.h" |
|
27 #include "MemSpyDriverObjectsInternal.h" |
|
28 |
|
29 // User includes |
|
30 #include "MemSpyDriverUtils.h" |
|
31 #include "MemSpyDriverOSAdaption.h" |
|
32 #include "MemSpyDriverSuspensionManager.h" |
|
33 |
|
34 // Constants |
|
35 const TBool KMemSpyDriverAllowDeadOpenRequests = ETrue; |
|
36 const TInt KMemSpyDriverLogChanThreadAndProcessXferBufferSize = 512; |
|
37 |
|
38 |
|
39 DMemSpyDriverLogChanThreadAndProcess::DMemSpyDriverLogChanThreadAndProcess( DMemSpyDriverDevice& aDevice, DThread& aThread ) |
|
40 : DMemSpyDriverLogChanBase( aDevice, aThread ) |
|
41 { |
|
42 TRACE( Kern::Printf("DMemSpyDriverLogChanThreadAndProcess::DMemSpyDriverLogChanThreadAndProcess() - this: 0x%08x", this )); |
|
43 } |
|
44 |
|
45 |
|
46 DMemSpyDriverLogChanThreadAndProcess::~DMemSpyDriverLogChanThreadAndProcess() |
|
47 { |
|
48 TRACE( Kern::Printf("DMemSpyDriverLogChanThreadAndProcess::~DMemSpyDriverLogChanThreadAndProcess() - START - this: 0x%08x", this )); |
|
49 |
|
50 TRACE( Kern::Printf("DMemSpyDriverLogChanThreadAndProcess::~DMemSpyDriverLogChanThreadAndProcess() - END - this: 0x%08x", this )); |
|
51 } |
|
52 |
|
53 |
|
54 TInt DMemSpyDriverLogChanThreadAndProcess::Construct() |
|
55 { |
|
56 TRACE( Kern::Printf("DMemSpyDriverLogChanThreadAndProcess::Construct() - START - this: 0x%08x", this )); |
|
57 |
|
58 const TInt ret = BaseConstruct( KMemSpyDriverLogChanThreadAndProcessXferBufferSize ); |
|
59 |
|
60 TRACE( Kern::Printf("DMemSpyDriverLogChanThreadAndProcess::Construct() - END - this: 0x%08x, err: %d", this, ret )); |
|
61 return ret; |
|
62 } |
|
63 |
|
64 |
|
65 TInt DMemSpyDriverLogChanThreadAndProcess::Request( TInt aFunction, TAny* a1, TAny* a2 ) |
|
66 { |
|
67 TInt r = DMemSpyDriverLogChanBase::Request( aFunction, a1, a2 ); |
|
68 if ( r == KErrNone ) |
|
69 { |
|
70 switch( aFunction ) |
|
71 { |
|
72 case EMemSpyDriverOpCodeThreadAndProcessGetInfoThread: |
|
73 r = GetInfoThread( (TUint)a1, (TMemSpyDriverInternalThreadInfoParams*) a2); |
|
74 break; |
|
75 case EMemSpyDriverOpCodeThreadAndProcessGetInfoProcess: |
|
76 r = GetInfoProcess( (TUint)a1, (TMemSpyDriverProcessInfo*) a2); |
|
77 break; |
|
78 case EMemSpyDriverOpCodeThreadAndProcessEndThread: |
|
79 r = EndThread( (TUint) a1, (TExitType) ((TUint) a2) ); |
|
80 break; |
|
81 case EMemSpyDriverOpCodeThreadAndProcessOpenThread: |
|
82 r = OpenThread( (TUint) a1 ); |
|
83 break; |
|
84 case EMemSpyDriverOpCodeThreadAndProcessOpenProcess: |
|
85 r = OpenProcess( (TUint) a1 ); |
|
86 break; |
|
87 case EMemSpyDriverOpCodeThreadAndProcessSuspendAllThreads: |
|
88 r = SuspendAllThreadsInProcess( (TUint) a1 ); |
|
89 break; |
|
90 case EMemSpyDriverOpCodeThreadAndProcessResumeAllThreads: |
|
91 r = ResumeAllThreadsInProcess( (TUint) a1 ); |
|
92 break; |
|
93 case EMemSpyDriverOpCodeThreadAndProcessGetThreads: |
|
94 r = GetThreadsForProcess( (TUint) a1, (TDes8*) a2 ); |
|
95 break; |
|
96 case EMemSpyDriverOpCodeThreadAndProcessSetPriorityThread: |
|
97 r = SetPriority( (TUint) a1, (TThreadPriority) ((TUint) a2) ); |
|
98 break; |
|
99 |
|
100 default: |
|
101 r = KErrNotSupported; |
|
102 break; |
|
103 } |
|
104 } |
|
105 // |
|
106 return r; |
|
107 } |
|
108 |
|
109 |
|
110 TBool DMemSpyDriverLogChanThreadAndProcess::IsHandler( TInt aFunction ) const |
|
111 { |
|
112 return ( aFunction > EMemSpyDriverOpCodeThreadAndProcessBase && aFunction < EMemSpyDriverOpCodeThreadAndProcessEnd ); |
|
113 } |
|
114 |
|
115 |
|
116 |
|
117 |
|
118 |
|
119 |
|
120 TInt DMemSpyDriverLogChanThreadAndProcess::GetInfoThread( TUint aTid, TMemSpyDriverInternalThreadInfoParams* aParams ) |
|
121 { |
|
122 TRACE( Kern::Printf("DMemSpyDriverLogChanThreadAndProcess::GetInfoThread() - START")); |
|
123 |
|
124 TMemSpyDriverInternalThreadInfoParams params; |
|
125 TInt r = Kern::ThreadRawRead( &ClientThread(), aParams, ¶ms, sizeof(TMemSpyDriverInternalThreadInfoParams) ); |
|
126 if ( r != KErrNone ) |
|
127 { |
|
128 TRACE( Kern::Printf("DMemSpyDriverLogChanThreadAndProcess::GetInfoThread() - END - params read error: %d", r)); |
|
129 return r; |
|
130 } |
|
131 |
|
132 r = OpenTempObject( aTid, EThread ); |
|
133 if ( r == KErrNone ) |
|
134 { |
|
135 DThread* dThread = (DThread*) TempObject(); |
|
136 NKern::ThreadEnterCS(); |
|
137 |
|
138 // Get DThread adaptor. This is a means of querying DThread internals |
|
139 // without accessing them directly. Takes into account possible differences |
|
140 // between compile time and run time |
|
141 DMemSpyDriverOSAdaptionDThread& dThreadAdaptor = OSAdaption().DThread(); |
|
142 |
|
143 // Saved CPU registers for thread. Need to get NThread to do this. |
|
144 NThread* nThread = dThreadAdaptor.GetNThread( *dThread ); |
|
145 TRACE( Kern::Printf("DMemSpyDriverLogChanThreadAndProcess::GetInfoThread() - getting regs..." )); |
|
146 MemSpyDriverUtils::GetThreadRegisters( nThread, params.iCpu ); |
|
147 |
|
148 // Name |
|
149 TRACE( Kern::Printf("DMemSpyDriverLogChanThreadAndProcess::GetInfoThread() - getting full name..." )); |
|
150 dThread->FullName( params.iFullName ); |
|
151 |
|
152 // User framework |
|
153 TRACE( Kern::Printf("DMemSpyDriverLogChanThreadAndProcess::GetInfoThread() - getting allocator..." )); |
|
154 params.iAllocator = dThreadAdaptor.GetAllocator( *dThread ); |
|
155 |
|
156 TRACE( Kern::Printf("DMemSpyDriverLogChanThreadAndProcess::GetInfoThread() - getting scheduler..." )); |
|
157 params.iScheduler = dThreadAdaptor.GetActiveScheduler( *dThread ); |
|
158 |
|
159 // User stack information - rest comes from user side API calls |
|
160 TRACE( Kern::Printf("DMemSpyDriverLogChanThreadAndProcess::GetInfoThread() - getting user stack pointer..." )); |
|
161 params.iStackInfo.iUserStackPointer = params.iCpu.iRn[ 12 ]; |
|
162 |
|
163 // Supervisor stack information |
|
164 TRACE( Kern::Printf("DMemSpyDriverLogChanThreadAndProcess::GetInfoThread() - getting supervisor stack..." )); |
|
165 params.iStackInfo.iSupervisorStackPointer = 0; |
|
166 params.iStackInfo.iSupervisorStackHighWatermark = 0; |
|
167 params.iStackInfo.iSupervisorStackBase = dThreadAdaptor.GetSupervisorStackBase( *dThread ); |
|
168 params.iStackInfo.iSupervisorStackSize = dThreadAdaptor.GetSupervisorStackSize( *dThread ); |
|
169 |
|
170 // Write back to user-side |
|
171 TRACE( Kern::Printf("DMemSpyDriverLogChanThreadAndProcess::GetInfoThread() - writing to user side..." )); |
|
172 r = Kern::ThreadRawWrite( &ClientThread(), aParams, ¶ms, sizeof(TMemSpyDriverInternalThreadInfoParams) ); |
|
173 NKern::ThreadLeaveCS(); |
|
174 CloseTempObject(); |
|
175 } |
|
176 |
|
177 TRACE( Kern::Printf("DMemSpyDriverLogChanThreadAndProcess::GetInfoThread() - END - ret: %d", r)); |
|
178 return r; |
|
179 } |
|
180 |
|
181 |
|
182 TInt DMemSpyDriverLogChanThreadAndProcess::GetInfoProcess( TUint aPid, TMemSpyDriverProcessInfo* aParams ) |
|
183 { |
|
184 TRACE( Kern::Printf("DMemSpyDriverLogChanThreadAndProcess::GetInfoProcess() - START")); |
|
185 |
|
186 TMemSpyDriverProcessInfo params; |
|
187 TInt r = Kern::ThreadRawRead( &ClientThread(), aParams, ¶ms, sizeof(TMemSpyDriverProcessInfo) ); |
|
188 if ( r != KErrNone ) |
|
189 { |
|
190 TRACE( Kern::Printf("DMemSpyDriverLogChanThreadAndProcess::GetInfoProcess() - END - params read error: %d", r)); |
|
191 return r; |
|
192 } |
|
193 |
|
194 r = OpenTempObject( aPid, EProcess ); |
|
195 if ( r == KErrNone ) |
|
196 { |
|
197 DMemSpyDriverOSAdaptionDProcess& processAdaptor = OSAdaption().DProcess(); |
|
198 |
|
199 DProcess* process = (DProcess*) TempObject(); |
|
200 NKern::ThreadEnterCS(); |
|
201 // |
|
202 params.iFlags = processAdaptor.GetFlags( *process ); |
|
203 params.iGeneration = processAdaptor.GetGeneration( *process ); |
|
204 params.iSecurityInfo = processAdaptor.GetSecurityInfo( *process ); |
|
205 |
|
206 // Write back to user-side |
|
207 r = Kern::ThreadRawWrite( &ClientThread(), aParams, ¶ms, sizeof(TMemSpyDriverProcessInfo) ); |
|
208 NKern::ThreadLeaveCS(); |
|
209 CloseTempObject(); |
|
210 } |
|
211 |
|
212 TRACE( Kern::Printf("DMemSpyDriverLogChanThreadAndProcess::GetInfoProcess() - END - ret: %d", r)); |
|
213 return r; |
|
214 } |
|
215 |
|
216 |
|
217 |
|
218 |
|
219 |
|
220 |
|
221 |
|
222 |
|
223 |
|
224 TInt DMemSpyDriverLogChanThreadAndProcess::EndThread( TUint aId, TExitType aType ) |
|
225 { |
|
226 TRACE( Kern::Printf("DMemSpyDriverLogChanThreadAndProcess::EndThread() - START - aId: %d, aType: %d", aId, aType )); |
|
227 |
|
228 TInt r = OpenTempObject( aId, EThread ); |
|
229 if ( r == KErrNone ) |
|
230 { |
|
231 DThread* thread = (DThread*) TempObject(); |
|
232 // |
|
233 const TInt reason = MapToMemSpyExitReason( aType ); |
|
234 Kern::ThreadKill( thread, aType, reason, KMemSpyClientPanic ); |
|
235 // |
|
236 CloseTempObject(); |
|
237 } |
|
238 else |
|
239 { |
|
240 r = KErrNotFound; |
|
241 } |
|
242 |
|
243 TRACE( Kern::Printf("DMemSpyDriverLogChanThreadAndProcess::EndThread() - END - r: %d", r)); |
|
244 return r; |
|
245 } |
|
246 |
|
247 |
|
248 |
|
249 |
|
250 |
|
251 |
|
252 |
|
253 |
|
254 |
|
255 |
|
256 |
|
257 |
|
258 |
|
259 |
|
260 |
|
261 |
|
262 |
|
263 |
|
264 |
|
265 |
|
266 |
|
267 |
|
268 TInt DMemSpyDriverLogChanThreadAndProcess::OpenThread( TUint aId ) |
|
269 { |
|
270 TRACE( Kern::Printf("DMemSpyDriverLogChanThreadAndProcess::OpenThread() - START - aId: %d", aId)); |
|
271 |
|
272 TInt r = OpenTempObject( aId, EThread, KMemSpyDriverAllowDeadOpenRequests ); |
|
273 TRACE( Kern::Printf("DMemSpyDriverLogChanThreadAndProcess::OpenThread() - done open temp object, r: %d", r )); |
|
274 if ( r == KErrNone ) |
|
275 { |
|
276 NKern::ThreadEnterCS(); |
|
277 DThread* threadToOpen = (DThread*) TempObject(); |
|
278 TRACE( Kern::Printf("DMemSpyDriverLogChanThreadAndProcess::OpenThread() - thread exit type: %d", threadToOpen->iExitType )); |
|
279 |
|
280 TRACE( Kern::Printf("DMemSpyDriverLogChanThreadAndProcess::OpenThread() - making handle..." )); |
|
281 r = Kern::MakeHandleAndOpen( &ClientThread(), threadToOpen ); |
|
282 TRACE( Kern::Printf("DMemSpyDriverLogChanThreadAndProcess::OpenThread() - handle: %d",r )); |
|
283 NKern::ThreadLeaveCS(); |
|
284 |
|
285 // Balance reference count, handle is still open and mapped into our process since we opened |
|
286 // it above via MakeHandleAndOpen |
|
287 TRACE( Kern::Printf("DMemSpyDriverLogChanThreadAndProcess::OpenThread() - closing temp object..." )); |
|
288 CloseTempObject(); |
|
289 TRACE( Kern::Printf("DMemSpyDriverLogChanThreadAndProcess::OpenThread() - closed temp object" )); |
|
290 } |
|
291 else |
|
292 { |
|
293 r = KErrNotFound; |
|
294 } |
|
295 |
|
296 TRACE( Kern::Printf("DMemSpyDriverLogChanThreadAndProcess::OpenThread() - END - r: %d", r)); |
|
297 return r; |
|
298 } |
|
299 |
|
300 |
|
301 TInt DMemSpyDriverLogChanThreadAndProcess::OpenProcess( TUint aId ) |
|
302 { |
|
303 TRACE( Kern::Printf("DMemSpyDriverLogChanThreadAndProcess::OpenProcess() - START - aId: %d", aId)); |
|
304 |
|
305 TInt r = OpenTempObject( aId, EProcess, KMemSpyDriverAllowDeadOpenRequests ); |
|
306 if ( r == KErrNone ) |
|
307 { |
|
308 NKern::ThreadEnterCS(); |
|
309 DProcess* processToOpen = (DProcess*) TempObject(); |
|
310 r = Kern::MakeHandleAndOpen( &ClientThread(), processToOpen ); |
|
311 NKern::ThreadLeaveCS(); |
|
312 |
|
313 // Balance reference count, handle is still open and mapped into our process since we opened |
|
314 // it above via MakeHandleAndOpen |
|
315 CloseTempObject(); |
|
316 } |
|
317 else |
|
318 { |
|
319 r = KErrNotFound; |
|
320 } |
|
321 |
|
322 TRACE( Kern::Printf("DMemSpyDriverLogChanThreadAndProcess::OpenProcess() - END - r: %d", r)); |
|
323 return r; |
|
324 } |
|
325 |
|
326 |
|
327 |
|
328 |
|
329 |
|
330 |
|
331 |
|
332 |
|
333 |
|
334 |
|
335 |
|
336 |
|
337 |
|
338 |
|
339 |
|
340 |
|
341 |
|
342 |
|
343 |
|
344 |
|
345 |
|
346 |
|
347 |
|
348 |
|
349 |
|
350 |
|
351 |
|
352 |
|
353 |
|
354 TInt DMemSpyDriverLogChanThreadAndProcess::SuspendAllThreadsInProcess( TUint aPid ) |
|
355 { |
|
356 TRACE( Kern::Printf( "DMemSpyDriverLogChanThreadAndProcess::SuspendAllThreadsInProcess() - START - aPid: %d", aPid )); |
|
357 |
|
358 DMemSpySuspensionManager& susMan = SuspensionManager(); |
|
359 const TInt err = susMan.SuspendAllThreadsInProcess( aPid, ClientThread() ); |
|
360 |
|
361 TRACE( Kern::Printf( "DMemSpyDriverLogChanThreadAndProcess::SuspendAllThreadsInProcess() - END - aPid: %d, err: %d", aPid, err )); |
|
362 return err; |
|
363 } |
|
364 |
|
365 |
|
366 TInt DMemSpyDriverLogChanThreadAndProcess::ResumeAllThreadsInProcess( TUint aPid ) |
|
367 { |
|
368 TRACE( Kern::Printf( "DMemSpyDriverLogChanThreadAndProcess::ResumeAllThreadsInProcess() - START - aPid: %d", aPid )); |
|
369 |
|
370 DMemSpySuspensionManager& susMan = SuspensionManager(); |
|
371 const TInt err = susMan.ResumeAllThreadsInProcess( aPid, ClientThread() ); |
|
372 |
|
373 TRACE( Kern::Printf( "DMemSpyDriverLogChanThreadAndProcess::ResumeAllThreadsInProcess() - END - aPid: %d, err: %d", aPid, err )); |
|
374 return err; |
|
375 } |
|
376 |
|
377 |
|
378 TInt DMemSpyDriverLogChanThreadAndProcess::GetThreadsForProcess( TUint aPid, TDes8* aBufferSink ) |
|
379 { |
|
380 TRACE( Kern::Printf( "DMemSpyDriverLogChanThreadAndProcess::GetThreadsForProcess() - START - aPid: %d", aPid ) ); |
|
381 |
|
382 // We open the source thread or process, just to ensure it doesn't die underneath us... |
|
383 TInt r = OpenTempObject( aPid, EProcess ); |
|
384 if ( r == KErrNone ) |
|
385 { |
|
386 DProcess& process = TempObjectAsProcess(); |
|
387 |
|
388 // Open stream |
|
389 RMemSpyMemStreamWriter stream = OpenXferStream(); |
|
390 TRACE( Kern::Printf( "DMemSpyDriverLogChanThreadAndProcess::GetThreadsForProcess() - stream remaining: %d", stream.Remaining() ) ); |
|
391 |
|
392 // Save marker pos for the thread count - we'll update it after the loop |
|
393 TInt count = 0; |
|
394 TInt32* pCountMarkerThread = stream.WriteInt32( 0 ); |
|
395 |
|
396 DMemSpyDriverOSAdaptionDProcess& processAdaptor = OSAdaption().DProcess(); |
|
397 SDblQue& threadQueue = processAdaptor.GetThreadQueue( process ); |
|
398 SDblQueLink* pLink = threadQueue.First(); |
|
399 while( pLink != & threadQueue.iA && !stream.IsFull() ) |
|
400 { |
|
401 DThread* pT = processAdaptor.GetThread( pLink ); |
|
402 // |
|
403 if ( pT ) |
|
404 { |
|
405 const TUint tid = OSAdaption().DThread().GetId( *pT ); |
|
406 TRACE( Kern::Printf( "DMemSpyDriverLogChanThreadAndProcess::GetThreadsForProcess() - id: %d (0x%04x)", tid, tid ) ); |
|
407 stream.WriteUint32( tid ); |
|
408 } |
|
409 |
|
410 pLink = pLink->iNext; |
|
411 ++count; |
|
412 } |
|
413 |
|
414 if ( stream.IsFull() ) |
|
415 { |
|
416 Kern::Printf( "DMemSpyDriverLogChanThreadAndProcess::GetThreadsForProcess() - STREAM FULL - id: %d (0x%04x), thread: %O", process.iId, process.iId, &process ); |
|
417 } |
|
418 |
|
419 // Now write the count |
|
420 *pCountMarkerThread = count; |
|
421 |
|
422 // Tidy up |
|
423 r = stream.WriteAndClose( aBufferSink ); |
|
424 TRACE( Kern::Printf( "DMemSpyDriverLogChanThreadAndProcess::GetThreadsForProcess() - r: %d, count: %d", r, count )); |
|
425 |
|
426 CloseTempObject(); |
|
427 } |
|
428 |
|
429 TRACE( Kern::Printf( "DMemSpyDriverLogChanThreadAndProcess::GetThreadsForProcess() - END - aPid: %d, err: %d", aPid, r )); |
|
430 return r; |
|
431 } |
|
432 |
|
433 |
|
434 TInt DMemSpyDriverLogChanThreadAndProcess::SetPriority( TUint aId, TThreadPriority aPriority ) |
|
435 { |
|
436 TRACE( Kern::Printf("DMemSpyDriverLogChanThreadAndProcess::SetPriority(T) - START - aId: %d, aPriority: %d", aId, aPriority )); |
|
437 |
|
438 TInt r = OpenTempObject( aId, EThread ); |
|
439 TRACE( Kern::Printf("DMemSpyDriverLogChanThreadAndProcess::SetPriority(T) - done open temp object, r: %d", r )); |
|
440 if ( r == KErrNone ) |
|
441 { |
|
442 // Map user side thread priority to kernel-side absolute thread priority (typically 0-63) |
|
443 const TInt kernelThreadPri = MemSpyDriverUtils::MapToAbsoluteThreadPriority( aPriority ); |
|
444 TRACE( Kern::Printf("DMemSpyDriverLogChanThreadAndProcess::SetPriority(T) - user: %d, kernel absolute: %d", aPriority, kernelThreadPri )); |
|
445 |
|
446 if ( kernelThreadPri > 0 && kernelThreadPri < KNumPriorities ) |
|
447 { |
|
448 NKern::ThreadEnterCS(); |
|
449 DThread& thread = TempObjectAsThread(); |
|
450 r = Kern::SetThreadPriority( kernelThreadPri, &thread ); |
|
451 TRACE( Kern::Printf("DMemSpyDriverLogChanThreadAndProcess::SetPriority(T) - Kern::SetThreadPriority() returned: %d", r )); |
|
452 NKern::ThreadLeaveCS(); |
|
453 } |
|
454 else |
|
455 { |
|
456 // Error |
|
457 r = kernelThreadPri; |
|
458 } |
|
459 |
|
460 CloseTempObject(); |
|
461 } |
|
462 |
|
463 TRACE( Kern::Printf("DMemSpyDriverLogChanThreadAndProcess::SetPriority(T) - END - r: %d", r)); |
|
464 return r; |
|
465 } |
|
466 |
|
467 |
|
468 |
|
469 |
|
470 |
|
471 |
|
472 TInt DMemSpyDriverLogChanThreadAndProcess::MapToMemSpyExitReason( TExitType aType ) |
|
473 { |
|
474 TInt ret = 0; |
|
475 // |
|
476 switch( aType ) |
|
477 { |
|
478 default: |
|
479 case EExitKill: |
|
480 ret = EPanicForcedKill; |
|
481 break; |
|
482 case EExitTerminate: |
|
483 ret = EPanicForcedTerminate; |
|
484 break; |
|
485 case EExitPanic: |
|
486 ret = EPanicForcedPanic; |
|
487 break; |
|
488 } |
|
489 // |
|
490 return ret; |
|
491 } |
|
492 |
|
493 |
|
494 |
|
495 |
|
496 |
|
497 |
|
498 |
|
499 |
|
500 |
|
501 |
|
502 |
|
503 |
|
504 |
|
505 |
|
506 |
|
507 |
|
508 |
|
509 |
|
510 |
|
511 |